comparison auth/test/auth_crypto_test.c @ 264:04fee26ecce0

add authenticated JRPG conversation platform Add reusable auth/session storage, owned conversation recovery, guest quotas, admin workflows, URL-routed conversation UI, mobile frame support, and parallel browser acceptance. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 07:34:12 -0700
parents
children
comparison
equal deleted inserted replaced
263:ee04e4e69fed 264:04fee26ecce0
1 #include "auth/auth_crypto.h"
2
3 #include <assert.h>
4 #include <stdio.h>
5 #include <string.h>
6
7 static boolean is_lowercase_hex(const char *text, size_t length)
8 {
9 for (size_t i = 0; i < length; ++i)
10 {
11 if (!((text[i] >= '0' && text[i] <= '9') ||
12 (text[i] >= 'a' && text[i] <= 'f')))
13 {
14 return FALSE;
15 }
16 }
17 return TRUE;
18 }
19
20 static boolean is_base64url(const char *text, size_t length)
21 {
22 for (size_t i = 0; i < length; ++i)
23 {
24 if (!((text[i] >= 'A' && text[i] <= 'Z') ||
25 (text[i] >= 'a' && text[i] <= 'z') ||
26 (text[i] >= '0' && text[i] <= '9') ||
27 text[i] == '-' || text[i] == '_'))
28 {
29 return FALSE;
30 }
31 }
32 return TRUE;
33 }
34
35 static void test_password_hashes(void)
36 {
37 char first[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE];
38 char second[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE];
39
40 assert(Auth_Crypto_Password_Hash(
41 "correct horse battery staple", first, sizeof(first)) == AUTH_CRYPTO_OK);
42 assert(Auth_Crypto_Password_Hash(
43 "correct horse battery staple", second, sizeof(second)) == AUTH_CRYPTO_OK);
44 assert(strcmp(first, second) != 0);
45 const char *prefix = "zenbu-scrypt$v=1$N=32768$r=8$p=1$";
46 assert(strncmp(first, prefix, strlen(prefix)) == 0);
47 assert(memcmp(
48 first + strlen(prefix),
49 second + strlen(prefix),
50 AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2) != 0);
51 assert(Auth_Crypto_Password_Verify(
52 "correct horse battery staple", first) == AUTH_CRYPTO_OK);
53 assert(Auth_Crypto_Password_Verify(
54 "wrong password", first) == AUTH_CRYPTO_AUTHENTICATION_FAILED);
55
56 char malformed[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE];
57 strcpy(malformed, first);
58 malformed[0] = 'x';
59 assert(Auth_Crypto_Password_Verify(
60 "correct horse battery staple", malformed) == AUTH_CRYPTO_MALFORMED);
61 assert(Auth_Crypto_Password_Verify(
62 "correct horse battery staple", "not-a-password-hash") ==
63 AUTH_CRYPTO_MALFORMED);
64
65 char oversized[AUTH_CRYPTO_PASSWORD_MAX_BYTES + 2];
66 memset(oversized, 'p', sizeof(oversized));
67 oversized[sizeof(oversized) - 1] = '\0';
68 assert(Auth_Crypto_Password_Hash(
69 oversized, first, sizeof(first)) == AUTH_CRYPTO_PASSWORD_TOO_LONG);
70 assert(Auth_Crypto_Password_Verify(
71 oversized, second) == AUTH_CRYPTO_PASSWORD_TOO_LONG);
72
73 char too_small[8];
74 assert(Auth_Crypto_Password_Hash(
75 "password", too_small, sizeof(too_small)) ==
76 AUTH_CRYPTO_BUFFER_TOO_SMALL);
77 }
78
79 static void test_tokens(void)
80 {
81 char first[AUTH_CRYPTO_TOKEN_SIZE];
82 char second[AUTH_CRYPTO_TOKEN_SIZE];
83 assert(Auth_Crypto_Token_Generate(first, sizeof(first)) == AUTH_CRYPTO_OK);
84 assert(Auth_Crypto_Token_Generate(second, sizeof(second)) == AUTH_CRYPTO_OK);
85 assert(strlen(first) == AUTH_CRYPTO_TOKEN_SIZE - 1);
86 assert(is_base64url(first, strlen(first)));
87 assert(strchr(first, '=') == NULL);
88 assert(strcmp(first, second) != 0);
89
90 char first_digest[AUTH_CRYPTO_TOKEN_DIGEST_SIZE];
91 char repeated_digest[AUTH_CRYPTO_TOKEN_DIGEST_SIZE];
92 char second_digest[AUTH_CRYPTO_TOKEN_DIGEST_SIZE];
93 assert(Auth_Crypto_Token_Digest(
94 first, first_digest, sizeof(first_digest)) == AUTH_CRYPTO_OK);
95 assert(Auth_Crypto_Token_Digest(
96 first, repeated_digest, sizeof(repeated_digest)) == AUTH_CRYPTO_OK);
97 assert(Auth_Crypto_Token_Digest(
98 second, second_digest, sizeof(second_digest)) == AUTH_CRYPTO_OK);
99 assert(strlen(first_digest) == AUTH_CRYPTO_TOKEN_DIGEST_SIZE - 1);
100 assert(is_lowercase_hex(first_digest, strlen(first_digest)));
101 assert(strcmp(first_digest, repeated_digest) == 0);
102 assert(strcmp(first_digest, second_digest) != 0);
103
104 const char *digest_vector_token =
105 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
106 assert(Auth_Crypto_Token_Digest(
107 digest_vector_token, first_digest, sizeof(first_digest)) ==
108 AUTH_CRYPTO_OK);
109 assert(strcmp(
110 first_digest,
111 "0f007385b6f9d4b7eeb2748605afe1a984a0a3bfa3f014d09e2a784ce9e5cd1a") ==
112 0);
113 }
114
115 static void test_ip_binding_and_guest_cookies(void)
116 {
117 uint8 secret[AUTH_CRYPTO_COOKIE_SECRET_MIN_BYTES] = {
118 0x1f, 0x8c, 0x32, 0x99, 0x50, 0xe7, 0xa6, 0x21,
119 0x83, 0x44, 0xab, 0xcd, 0x72, 0x91, 0x05, 0xfe,
120 0x66, 0x3a, 0x10, 0x28, 0xdd, 0xc0, 0x4b, 0x7e,
121 0x59, 0x92, 0xb1, 0x13, 0xef, 0x70, 0x46, 0x8a,
122 };
123 const char *peer_ip = "2001:db8::1234";
124 const char *other_ip = "2001:db8::5678";
125
126 char ip_digest[AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE];
127 char repeated_ip_digest[AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE];
128 char other_ip_digest[AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE];
129 assert(Auth_Crypto_IP_Binding_Digest(
130 secret, sizeof(secret), peer_ip, ip_digest, sizeof(ip_digest)) ==
131 AUTH_CRYPTO_OK);
132 assert(Auth_Crypto_IP_Binding_Digest(
133 secret,
134 sizeof(secret),
135 peer_ip,
136 repeated_ip_digest,
137 sizeof(repeated_ip_digest)) == AUTH_CRYPTO_OK);
138 assert(Auth_Crypto_IP_Binding_Digest(
139 secret,
140 sizeof(secret),
141 other_ip,
142 other_ip_digest,
143 sizeof(other_ip_digest)) == AUTH_CRYPTO_OK);
144 assert(strlen(ip_digest) == AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE - 1);
145 assert(is_lowercase_hex(ip_digest, strlen(ip_digest)));
146 assert(strcmp(ip_digest, repeated_ip_digest) == 0);
147 assert(strcmp(ip_digest, other_ip_digest) != 0);
148
149 const char *guest_uuid = "123e4567-e89b-12d3-a456-426614174000";
150 uint64 expiration = 2000000000;
151 char cookie[AUTH_CRYPTO_GUEST_COOKIE_SIZE];
152 assert(Auth_Crypto_Guest_Cookie_Create(
153 secret,
154 sizeof(secret),
155 guest_uuid,
156 expiration,
157 ip_digest,
158 cookie,
159 sizeof(cookie)) == AUTH_CRYPTO_OK);
160 assert(strstr(cookie, peer_ip) == NULL);
161
162 Auth_Crypto_Guest_Cookie verified;
163 assert(Auth_Crypto_Guest_Cookie_Verify(
164 secret,
165 sizeof(secret),
166 cookie,
167 expiration - 1,
168 ip_digest,
169 &verified) == AUTH_CRYPTO_OK);
170 assert(strcmp(verified.guest_uuid, guest_uuid) == 0);
171 assert(verified.expiration_unix == expiration);
172
173 char tampered[AUTH_CRYPTO_GUEST_COOKIE_SIZE];
174 strcpy(tampered, cookie);
175 size_t cookie_length = strlen(tampered);
176 tampered[cookie_length - 1] =
177 tampered[cookie_length - 1] == '0' ? '1' : '0';
178 assert(Auth_Crypto_Guest_Cookie_Verify(
179 secret,
180 sizeof(secret),
181 tampered,
182 expiration - 1,
183 ip_digest,
184 &verified) == AUTH_CRYPTO_AUTHENTICATION_FAILED);
185
186 assert(Auth_Crypto_Guest_Cookie_Verify(
187 secret,
188 sizeof(secret),
189 cookie,
190 expiration,
191 ip_digest,
192 &verified) == AUTH_CRYPTO_EXPIRED);
193 assert(Auth_Crypto_Guest_Cookie_Verify(
194 secret,
195 sizeof(secret),
196 cookie,
197 expiration - 1,
198 other_ip_digest,
199 &verified) == AUTH_CRYPTO_IP_MISMATCH);
200 assert(Auth_Crypto_Guest_Cookie_Verify(
201 secret,
202 sizeof(secret),
203 "v1.malformed",
204 expiration - 1,
205 ip_digest,
206 &verified) == AUTH_CRYPTO_MALFORMED);
207 }
208
209 int main(void)
210 {
211 test_password_hashes();
212 test_tokens();
213 test_ip_binding_and_guest_cookies();
214
215 /* Auth_Crypto_Base64url_Encode */
216 {
217 /* 32 bytes → 43 unpadded base64url chars */
218 uint8 bytes32[32] = {0};
219 char out[AUTH_CRYPTO_TOKEN_SIZE];
220 size_t n = Auth_Crypto_Base64url_Encode(bytes32, 32, out, sizeof(out));
221 assert(n == AUTH_CRYPTO_TOKEN_SIZE - 1);
222 assert(strlen(out) == AUTH_CRYPTO_TOKEN_SIZE - 1);
223
224 /* 3 bytes → 4 chars */
225 uint8 b3[3] = {0xfb, 0xff, 0xfe};
226 char o3[8];
227 assert(Auth_Crypto_Base64url_Encode(b3, 3, o3, sizeof(o3)) == 4);
228
229 /* 1 byte → 2 chars */
230 uint8 b1[1] = {0x00};
231 char o1[4];
232 assert(Auth_Crypto_Base64url_Encode(b1, 1, o1, sizeof(o1)) == 2);
233
234 /* 2 bytes → 3 chars */
235 uint8 b2[2] = {0x00, 0x00};
236 char o2[4];
237 assert(Auth_Crypto_Base64url_Encode(b2, 2, o2, sizeof(o2)) == 3);
238
239 /* Buffer too small → 0 */
240 char oSmall[3];
241 assert(Auth_Crypto_Base64url_Encode(bytes32, 32, oSmall, sizeof(oSmall)) == 0);
242
243 /* Output uses URL-safe alphabet only */
244 uint8 bRnd[32];
245 for (int i = 0; i < 32; i++) bRnd[i] = (uint8)i;
246 char oRnd[AUTH_CRYPTO_TOKEN_SIZE];
247 n = Auth_Crypto_Base64url_Encode(bRnd, 32, oRnd, sizeof(oRnd));
248 assert(n == 43);
249 for (size_t i = 0; i < n; i++) {
250 char c = oRnd[i];
251 assert((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
252 (c >= '0' && c <= '9') || c == '-' || c == '_');
253 }
254 }
255 puts("Auth_Crypto_Base64url_Encode: PASS");
256
257 puts("auth_crypto_test: PASS");
258 return 0;
259 }