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;
+}