Mercurial
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/auth/test/auth_crypto_test.c Fri Aug 07 07:34:12 2026 -0700 @@ -0,0 +1,259 @@ +#include "auth/auth_crypto.h" + +#include <assert.h> +#include <stdio.h> +#include <string.h> + +static boolean is_lowercase_hex(const char *text, size_t length) +{ + for (size_t i = 0; i < length; ++i) + { + if (!((text[i] >= '0' && text[i] <= '9') || + (text[i] >= 'a' && text[i] <= 'f'))) + { + return FALSE; + } + } + return TRUE; +} + +static boolean is_base64url(const char *text, size_t length) +{ + for (size_t i = 0; i < length; ++i) + { + if (!((text[i] >= 'A' && text[i] <= 'Z') || + (text[i] >= 'a' && text[i] <= 'z') || + (text[i] >= '0' && text[i] <= '9') || + text[i] == '-' || text[i] == '_')) + { + return FALSE; + } + } + return TRUE; +} + +static void test_password_hashes(void) +{ + char first[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE]; + char second[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE]; + + assert(Auth_Crypto_Password_Hash( + "correct horse battery staple", first, sizeof(first)) == AUTH_CRYPTO_OK); + assert(Auth_Crypto_Password_Hash( + "correct horse battery staple", second, sizeof(second)) == AUTH_CRYPTO_OK); + assert(strcmp(first, second) != 0); + const char *prefix = "zenbu-scrypt$v=1$N=32768$r=8$p=1$"; + assert(strncmp(first, prefix, strlen(prefix)) == 0); + assert(memcmp( + first + strlen(prefix), + second + strlen(prefix), + AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2) != 0); + assert(Auth_Crypto_Password_Verify( + "correct horse battery staple", first) == AUTH_CRYPTO_OK); + assert(Auth_Crypto_Password_Verify( + "wrong password", first) == AUTH_CRYPTO_AUTHENTICATION_FAILED); + + char malformed[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE]; + strcpy(malformed, first); + malformed[0] = 'x'; + assert(Auth_Crypto_Password_Verify( + "correct horse battery staple", malformed) == AUTH_CRYPTO_MALFORMED); + assert(Auth_Crypto_Password_Verify( + "correct horse battery staple", "not-a-password-hash") == + AUTH_CRYPTO_MALFORMED); + + char oversized[AUTH_CRYPTO_PASSWORD_MAX_BYTES + 2]; + memset(oversized, 'p', sizeof(oversized)); + oversized[sizeof(oversized) - 1] = '\0'; + assert(Auth_Crypto_Password_Hash( + oversized, first, sizeof(first)) == AUTH_CRYPTO_PASSWORD_TOO_LONG); + assert(Auth_Crypto_Password_Verify( + oversized, second) == AUTH_CRYPTO_PASSWORD_TOO_LONG); + + char too_small[8]; + assert(Auth_Crypto_Password_Hash( + "password", too_small, sizeof(too_small)) == + AUTH_CRYPTO_BUFFER_TOO_SMALL); +} + +static void test_tokens(void) +{ + char first[AUTH_CRYPTO_TOKEN_SIZE]; + char second[AUTH_CRYPTO_TOKEN_SIZE]; + assert(Auth_Crypto_Token_Generate(first, sizeof(first)) == AUTH_CRYPTO_OK); + assert(Auth_Crypto_Token_Generate(second, sizeof(second)) == AUTH_CRYPTO_OK); + assert(strlen(first) == AUTH_CRYPTO_TOKEN_SIZE - 1); + assert(is_base64url(first, strlen(first))); + assert(strchr(first, '=') == NULL); + assert(strcmp(first, second) != 0); + + char first_digest[AUTH_CRYPTO_TOKEN_DIGEST_SIZE]; + char repeated_digest[AUTH_CRYPTO_TOKEN_DIGEST_SIZE]; + char second_digest[AUTH_CRYPTO_TOKEN_DIGEST_SIZE]; + assert(Auth_Crypto_Token_Digest( + first, first_digest, sizeof(first_digest)) == AUTH_CRYPTO_OK); + assert(Auth_Crypto_Token_Digest( + first, repeated_digest, sizeof(repeated_digest)) == AUTH_CRYPTO_OK); + assert(Auth_Crypto_Token_Digest( + second, second_digest, sizeof(second_digest)) == AUTH_CRYPTO_OK); + assert(strlen(first_digest) == AUTH_CRYPTO_TOKEN_DIGEST_SIZE - 1); + assert(is_lowercase_hex(first_digest, strlen(first_digest))); + assert(strcmp(first_digest, repeated_digest) == 0); + assert(strcmp(first_digest, second_digest) != 0); + + const char *digest_vector_token = + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + assert(Auth_Crypto_Token_Digest( + digest_vector_token, first_digest, sizeof(first_digest)) == + AUTH_CRYPTO_OK); + assert(strcmp( + first_digest, + "0f007385b6f9d4b7eeb2748605afe1a984a0a3bfa3f014d09e2a784ce9e5cd1a") == + 0); +} + +static void test_ip_binding_and_guest_cookies(void) +{ + uint8 secret[AUTH_CRYPTO_COOKIE_SECRET_MIN_BYTES] = { + 0x1f, 0x8c, 0x32, 0x99, 0x50, 0xe7, 0xa6, 0x21, + 0x83, 0x44, 0xab, 0xcd, 0x72, 0x91, 0x05, 0xfe, + 0x66, 0x3a, 0x10, 0x28, 0xdd, 0xc0, 0x4b, 0x7e, + 0x59, 0x92, 0xb1, 0x13, 0xef, 0x70, 0x46, 0x8a, + }; + const char *peer_ip = "2001:db8::1234"; + const char *other_ip = "2001:db8::5678"; + + char ip_digest[AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE]; + char repeated_ip_digest[AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE]; + char other_ip_digest[AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE]; + assert(Auth_Crypto_IP_Binding_Digest( + secret, sizeof(secret), peer_ip, ip_digest, sizeof(ip_digest)) == + AUTH_CRYPTO_OK); + assert(Auth_Crypto_IP_Binding_Digest( + secret, + sizeof(secret), + peer_ip, + repeated_ip_digest, + sizeof(repeated_ip_digest)) == AUTH_CRYPTO_OK); + assert(Auth_Crypto_IP_Binding_Digest( + secret, + sizeof(secret), + other_ip, + other_ip_digest, + sizeof(other_ip_digest)) == AUTH_CRYPTO_OK); + assert(strlen(ip_digest) == AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE - 1); + assert(is_lowercase_hex(ip_digest, strlen(ip_digest))); + assert(strcmp(ip_digest, repeated_ip_digest) == 0); + assert(strcmp(ip_digest, other_ip_digest) != 0); + + const char *guest_uuid = "123e4567-e89b-12d3-a456-426614174000"; + uint64 expiration = 2000000000; + char cookie[AUTH_CRYPTO_GUEST_COOKIE_SIZE]; + assert(Auth_Crypto_Guest_Cookie_Create( + secret, + sizeof(secret), + guest_uuid, + expiration, + ip_digest, + cookie, + sizeof(cookie)) == AUTH_CRYPTO_OK); + assert(strstr(cookie, peer_ip) == NULL); + + Auth_Crypto_Guest_Cookie verified; + assert(Auth_Crypto_Guest_Cookie_Verify( + secret, + sizeof(secret), + cookie, + expiration - 1, + ip_digest, + &verified) == AUTH_CRYPTO_OK); + assert(strcmp(verified.guest_uuid, guest_uuid) == 0); + assert(verified.expiration_unix == expiration); + + char tampered[AUTH_CRYPTO_GUEST_COOKIE_SIZE]; + strcpy(tampered, cookie); + size_t cookie_length = strlen(tampered); + tampered[cookie_length - 1] = + tampered[cookie_length - 1] == '0' ? '1' : '0'; + assert(Auth_Crypto_Guest_Cookie_Verify( + secret, + sizeof(secret), + tampered, + expiration - 1, + ip_digest, + &verified) == AUTH_CRYPTO_AUTHENTICATION_FAILED); + + assert(Auth_Crypto_Guest_Cookie_Verify( + secret, + sizeof(secret), + cookie, + expiration, + ip_digest, + &verified) == AUTH_CRYPTO_EXPIRED); + assert(Auth_Crypto_Guest_Cookie_Verify( + secret, + sizeof(secret), + cookie, + expiration - 1, + other_ip_digest, + &verified) == AUTH_CRYPTO_IP_MISMATCH); + assert(Auth_Crypto_Guest_Cookie_Verify( + secret, + sizeof(secret), + "v1.malformed", + expiration - 1, + ip_digest, + &verified) == AUTH_CRYPTO_MALFORMED); +} + +int main(void) +{ + test_password_hashes(); + test_tokens(); + test_ip_binding_and_guest_cookies(); + + /* Auth_Crypto_Base64url_Encode */ + { + /* 32 bytes → 43 unpadded base64url chars */ + uint8 bytes32[32] = {0}; + char out[AUTH_CRYPTO_TOKEN_SIZE]; + size_t n = Auth_Crypto_Base64url_Encode(bytes32, 32, out, sizeof(out)); + assert(n == AUTH_CRYPTO_TOKEN_SIZE - 1); + assert(strlen(out) == AUTH_CRYPTO_TOKEN_SIZE - 1); + + /* 3 bytes → 4 chars */ + uint8 b3[3] = {0xfb, 0xff, 0xfe}; + char o3[8]; + assert(Auth_Crypto_Base64url_Encode(b3, 3, o3, sizeof(o3)) == 4); + + /* 1 byte → 2 chars */ + uint8 b1[1] = {0x00}; + char o1[4]; + assert(Auth_Crypto_Base64url_Encode(b1, 1, o1, sizeof(o1)) == 2); + + /* 2 bytes → 3 chars */ + uint8 b2[2] = {0x00, 0x00}; + char o2[4]; + assert(Auth_Crypto_Base64url_Encode(b2, 2, o2, sizeof(o2)) == 3); + + /* Buffer too small → 0 */ + char oSmall[3]; + assert(Auth_Crypto_Base64url_Encode(bytes32, 32, oSmall, sizeof(oSmall)) == 0); + + /* Output uses URL-safe alphabet only */ + uint8 bRnd[32]; + for (int i = 0; i < 32; i++) bRnd[i] = (uint8)i; + char oRnd[AUTH_CRYPTO_TOKEN_SIZE]; + n = Auth_Crypto_Base64url_Encode(bRnd, 32, oRnd, sizeof(oRnd)); + assert(n == 43); + for (size_t i = 0; i < n; i++) { + char c = oRnd[i]; + assert((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '-' || c == '_'); + } + } + puts("Auth_Crypto_Base64url_Encode: PASS"); + + puts("auth_crypto_test: PASS"); + return 0; +}