Mercurial
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 } |