comparison auth/auth_http.c @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents
children
comparison
equal deleted inserted replaced
278:8d560f50ed4c 279:b3b547563ec7
1 #include "auth/auth_http.h"
2
3 #include <openssl/crypto.h>
4 #include <openssl/evp.h>
5 #include <openssl/hmac.h>
6
7 #include <string.h>
8
9 #define AUTH_HTTP_COOKIE_VALUE_MAX 512
10 #define AUTH_HTTP_CSRF_SUFFIX ":csrf:v1"
11 #define AUTH_HTTP_BINDING_INPUT_MAX (AUTH_CRYPTO_TOKEN_DIGEST_SIZE + 16)
12
13 const char *Auth_HTTP_Request_Value(
14 Seobeo_Request_Entry *p_request,
15 const char *key)
16 {
17 if (!p_request || !key)
18 return NULL;
19 void *p_value = Dowa_HashMap_Get_Ptr(p_request, (char *)key);
20 return p_value ? ((Seobeo_Request_Entry *)p_value)->value : NULL;
21 }
22
23 boolean Auth_HTTP_Parse_Cookie(
24 const char *cookie_header,
25 const char *name,
26 char *value_out,
27 size_t capacity)
28 {
29 if (!value_out || capacity == 0)
30 return FALSE;
31 value_out[0] = '\0';
32 if (!cookie_header || !name || name[0] == '\0')
33 return FALSE;
34 size_t name_length = strlen(name);
35 const char *cursor = cookie_header;
36
37 while (*cursor)
38 {
39 while (*cursor == ' ' || *cursor == '\t')
40 cursor++;
41
42 if (strncmp(cursor, name, name_length) == 0 &&
43 cursor[name_length] == '=')
44 {
45 cursor += name_length + 1;
46 const char *start = cursor;
47 while (*cursor && *cursor != ';')
48 cursor++;
49 size_t value_length = (size_t)(cursor - start);
50 if (value_length >= capacity)
51 return FALSE;
52 memcpy(value_out, start, value_length);
53 value_out[value_length] = '\0';
54 return TRUE;
55 }
56
57 while (*cursor && *cursor != ';')
58 cursor++;
59 if (*cursor == ';')
60 cursor++;
61 }
62 return FALSE;
63 }
64
65 boolean Auth_HTTP_Same_Origin(Seobeo_Request_Entry *p_request)
66 {
67 const char *host = Auth_HTTP_Request_Value(p_request, "Host");
68 const char *origin = Auth_HTTP_Request_Value(p_request, "Origin");
69 if (!host || !origin)
70 return FALSE;
71
72 const char *origin_host = strstr(origin, "://");
73 if (!origin_host)
74 return FALSE;
75 origin_host += 3;
76
77 const char *end = strchr(origin_host, '/');
78 size_t origin_host_length =
79 end ? (size_t)(end - origin_host) : strlen(origin_host);
80 return strlen(host) == origin_host_length &&
81 strncmp(host, origin_host, origin_host_length) == 0;
82 }
83
84 boolean Auth_HTTP_Derive_CSRF(
85 const uint8 *cookie_secret,
86 size_t cookie_secret_length,
87 const char *binding,
88 char *csrf_out,
89 size_t csrf_capacity)
90 {
91 if (!cookie_secret ||
92 cookie_secret_length < AUTH_CRYPTO_COOKIE_SECRET_MIN_BYTES ||
93 cookie_secret_length > AUTH_CRYPTO_COOKIE_SECRET_MAX_BYTES ||
94 !binding || !csrf_out || csrf_capacity < AUTH_CRYPTO_TOKEN_SIZE)
95 return FALSE;
96
97 size_t binding_length = strlen(binding);
98 size_t suffix_length = strlen(AUTH_HTTP_CSRF_SUFFIX);
99 size_t input_length = binding_length + suffix_length;
100 if (input_length >= AUTH_HTTP_BINDING_INPUT_MAX)
101 return FALSE;
102
103 char input[AUTH_HTTP_BINDING_INPUT_MAX] = {0};
104 memcpy(input, binding, binding_length);
105 memcpy(input + binding_length, AUTH_HTTP_CSRF_SUFFIX, suffix_length);
106
107 uint8 digest[32] = {0};
108 uint32 digest_length = sizeof(digest);
109 if (!HMAC(EVP_sha256(), cookie_secret, (int)cookie_secret_length,
110 (const uint8 *)input, input_length, digest, &digest_length))
111 {
112 OPENSSL_cleanse(input, sizeof(input));
113 OPENSSL_cleanse(digest, sizeof(digest));
114 return FALSE;
115 }
116 OPENSSL_cleanse(input, sizeof(input));
117
118 size_t encoded_length = Auth_Crypto_Base64url_Encode(
119 digest, sizeof(digest), csrf_out, csrf_capacity);
120 OPENSSL_cleanse(digest, sizeof(digest));
121 return encoded_length == AUTH_CRYPTO_TOKEN_SIZE - 1;
122 }
123
124 boolean Auth_HTTP_Verify_CSRF(
125 Seobeo_Request_Entry *p_request,
126 const uint8 *cookie_secret,
127 size_t cookie_secret_length,
128 const char *binding)
129 {
130 if (!Auth_HTTP_Same_Origin(p_request) || !binding)
131 return FALSE;
132
133 const char *provided = Auth_HTTP_Request_Value(p_request, "X-CSRF-Token");
134 return Auth_HTTP_Verify_CSRF_Token(
135 cookie_secret, cookie_secret_length, binding, provided);
136 }
137
138 boolean Auth_HTTP_Verify_CSRF_Token(
139 const uint8 *cookie_secret,
140 size_t cookie_secret_length,
141 const char *binding,
142 const char *provided_token)
143 {
144 if (!binding || !provided_token || provided_token[0] == '\0')
145 return FALSE;
146 char expected[AUTH_CRYPTO_TOKEN_SIZE] = {0};
147 char provided_digest[AUTH_CRYPTO_TOKEN_DIGEST_SIZE] = {0};
148 char expected_digest[AUTH_CRYPTO_TOKEN_DIGEST_SIZE] = {0};
149 boolean valid = FALSE;
150
151 if (Auth_HTTP_Derive_CSRF(
152 cookie_secret, cookie_secret_length, binding,
153 expected, sizeof(expected)) &&
154 Auth_Crypto_Token_Digest(
155 provided_token, provided_digest, sizeof(provided_digest)) ==
156 AUTH_CRYPTO_OK &&
157 Auth_Crypto_Token_Digest(
158 expected, expected_digest, sizeof(expected_digest)) ==
159 AUTH_CRYPTO_OK)
160 {
161 valid = CRYPTO_memcmp(
162 provided_digest, expected_digest, sizeof(provided_digest)) == 0;
163 }
164
165 OPENSSL_cleanse(expected, sizeof(expected));
166 OPENSSL_cleanse(provided_digest, sizeof(provided_digest));
167 OPENSSL_cleanse(expected_digest, sizeof(expected_digest));
168 return valid;
169 }
170
171 Auth_HTTP_Resolve_Result Auth_HTTP_Resolve_Authenticated_User(
172 Seobeo_Request_Entry *p_request,
173 Auth_Store *p_store,
174 const uint8 *cookie_secret,
175 size_t cookie_secret_length,
176 int64 current_unix,
177 int64 session_idle_ttl_secs,
178 Auth_HTTP_Authenticated_User *p_user)
179 {
180 if (!p_user)
181 return AUTH_HTTP_RESOLVE_ERROR;
182 memset(p_user, 0, sizeof(*p_user));
183 if (!p_request || !p_store || !cookie_secret ||
184 cookie_secret_length < AUTH_CRYPTO_COOKIE_SECRET_MIN_BYTES ||
185 cookie_secret_length > AUTH_CRYPTO_COOKIE_SECRET_MAX_BYTES ||
186 current_unix < 0 || session_idle_ttl_secs <= 0)
187 return AUTH_HTTP_RESOLVE_ERROR;
188
189 char session_token[AUTH_HTTP_COOKIE_VALUE_MAX] = {0};
190 const char *cookie_header = Auth_HTTP_Request_Value(p_request, "Cookie");
191 if (!cookie_header ||
192 !Auth_HTTP_Parse_Cookie(
193 cookie_header, AUTH_HTTP_SESSION_COOKIE_NAME,
194 session_token, sizeof(session_token)) ||
195 session_token[0] == '\0')
196 {
197 OPENSSL_cleanse(session_token, sizeof(session_token));
198 return AUTH_HTTP_RESOLVE_NOT_FOUND;
199 }
200
201 if (Auth_Crypto_Token_Digest(
202 session_token, p_user->token_digest,
203 sizeof(p_user->token_digest)) != AUTH_CRYPTO_OK)
204 {
205 OPENSSL_cleanse(session_token, sizeof(session_token));
206 OPENSSL_cleanse(p_user, sizeof(*p_user));
207 return AUTH_HTTP_RESOLVE_NOT_FOUND;
208 }
209 OPENSSL_cleanse(session_token, sizeof(session_token));
210
211 Auth_Store_Result result = Auth_Store_Find_Session(
212 p_store, p_user->token_digest, current_unix,
213 &p_user->session, &p_user->user);
214 if (result != AUTH_STORE_OK)
215 {
216 OPENSSL_cleanse(p_user, sizeof(*p_user));
217 return result == AUTH_STORE_ERROR
218 ? AUTH_HTTP_RESOLVE_ERROR
219 : AUTH_HTTP_RESOLVE_NOT_FOUND;
220 }
221
222 Auth_Store_Touch_Session(
223 p_store, p_user->token_digest, current_unix, session_idle_ttl_secs);
224 if (!Auth_HTTP_Derive_CSRF(
225 cookie_secret, cookie_secret_length, p_user->token_digest,
226 p_user->csrf_token, sizeof(p_user->csrf_token)))
227 {
228 OPENSSL_cleanse(p_user, sizeof(*p_user));
229 return AUTH_HTTP_RESOLVE_ERROR;
230 }
231 return AUTH_HTTP_RESOLVE_OK;
232 }