view auth/auth_crypto.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 04fee26ecce0
children
line wrap: on
line source

#include "auth/auth_crypto.h"

#include <stdio.h>
#include <string.h>

#include <openssl/crypto.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>

#define AUTH_CRYPTO_SCRYPT_N 32768
#define AUTH_CRYPTO_SCRYPT_R 8
#define AUTH_CRYPTO_SCRYPT_P 1
#define AUTH_CRYPTO_SCRYPT_MAXMEM (64ULL * 1024ULL * 1024ULL)

#define AUTH_CRYPTO_PASSWORD_PREFIX "zenbu-scrypt$v=1$N=32768$r=8$p=1$"
#define AUTH_CRYPTO_COOKIE_VERSION "v1"
#define AUTH_CRYPTO_COOKIE_HMAC_DOMAIN "zenbu-auth-guest-cookie-v1:"
#define AUTH_CRYPTO_IP_HMAC_DOMAIN "zenbu-auth-ip-binding-v1:"
#define AUTH_CRYPTO_HMAC_INPUT_SIZE 384

static boolean auth__bounded_length(
    const char *text,
    size_t maximum,
    size_t *p_length)
{
  if (!text || !p_length)
  {
    return FALSE;
  }

  for (size_t i = 0; i <= maximum; ++i)
  {
    if (text[i] == '\0')
    {
      *p_length = i;
      return TRUE;
    }
  }

  return FALSE;
}

static void auth__hex_encode(
    const uint8 *bytes,
    size_t byte_count,
    char *hex)
{
  static const char alphabet[] = "0123456789abcdef";

  for (size_t i = 0; i < byte_count; ++i)
  {
    hex[i * 2] = alphabet[bytes[i] >> 4];
    hex[i * 2 + 1] = alphabet[bytes[i] & 0x0f];
  }
  hex[byte_count * 2] = '\0';
}

static int auth__hex_value(char value)
{
  if (value >= '0' && value <= '9')
  {
    return value - '0';
  }
  if (value >= 'a' && value <= 'f')
  {
    return value - 'a' + 10;
  }
  return -1;
}

static boolean auth__hex_decode(
    const char *hex,
    size_t hex_length,
    uint8 *bytes,
    size_t byte_count)
{
  if (hex_length != byte_count * 2)
  {
    return FALSE;
  }

  for (size_t i = 0; i < byte_count; ++i)
  {
    int high = auth__hex_value(hex[i * 2]);
    int low = auth__hex_value(hex[i * 2 + 1]);
    if (high < 0 || low < 0)
    {
      return FALSE;
    }
    bytes[i] = (uint8)((high << 4) | low);
  }

  return TRUE;
}

static boolean auth__secret_is_valid(
    const uint8 *cookie_secret,
    size_t cookie_secret_length)
{
  return cookie_secret &&
         cookie_secret_length >= AUTH_CRYPTO_COOKIE_SECRET_MIN_BYTES &&
         cookie_secret_length <= AUTH_CRYPTO_COOKIE_SECRET_MAX_BYTES;
}

static boolean auth__uuid_is_valid(const char *uuid, size_t uuid_length)
{
  if (uuid_length != AUTH_CRYPTO_GUEST_UUID_SIZE - 1)
  {
    return FALSE;
  }

  for (size_t i = 0; i < uuid_length; ++i)
  {
    if (i == 8 || i == 13 || i == 18 || i == 23)
    {
      if (uuid[i] != '-')
      {
        return FALSE;
      }
    }
    else if (auth__hex_value(uuid[i]) < 0)
    {
      return FALSE;
    }
  }

  return TRUE;
}

static boolean auth__parse_uint64(
    const char *digits,
    size_t digit_count,
    uint64 *p_value)
{
  if (!digits || !p_value || digit_count == 0 || digit_count > 20 ||
      (digit_count > 1 && digits[0] == '0'))
  {
    return FALSE;
  }

  uint64 value = 0;
  uint64 maximum = (uint64)-1;
  for (size_t i = 0; i < digit_count; ++i)
  {
    if (digits[i] < '0' || digits[i] > '9')
    {
      return FALSE;
    }

    uint8 digit = (uint8)(digits[i] - '0');
    if (value > (maximum - digit) / 10)
    {
      return FALSE;
    }
    value = value * 10 + digit;
  }

  *p_value = value;
  return TRUE;
}

static Auth_Crypto_Result auth__hmac_sha256(
    const uint8 *secret,
    size_t secret_length,
    const char *domain,
    const char *value,
    size_t value_length,
    uint8 digest[32])
{
  size_t domain_length = strlen(domain);
  if (domain_length + value_length > AUTH_CRYPTO_HMAC_INPUT_SIZE)
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }

  uint8 input[AUTH_CRYPTO_HMAC_INPUT_SIZE];
  memcpy(input, domain, domain_length);
  memcpy(input + domain_length, value, value_length);

  unsigned int digest_length = 0;
  uint8 *result = HMAC(
      EVP_sha256(),
      secret,
      (int)secret_length,
      input,
      domain_length + value_length,
      digest,
      &digest_length);
  OPENSSL_cleanse(input, sizeof(input));

  if (!result || digest_length != 32)
  {
    OPENSSL_cleanse(digest, 32);
    return AUTH_CRYPTO_OPERATION_FAILED;
  }

  return AUTH_CRYPTO_OK;
}

static boolean auth__split_cookie(
    const char *cookie,
    size_t cookie_length,
    const char *segments[5],
    size_t segment_lengths[5])
{
  size_t segment_start = 0;
  size_t segment_count = 0;

  for (size_t i = 0; i <= cookie_length; ++i)
  {
    if (i == cookie_length || cookie[i] == '.')
    {
      if (segment_count >= 5 || i == segment_start)
      {
        return FALSE;
      }
      segments[segment_count] = cookie + segment_start;
      segment_lengths[segment_count] = i - segment_start;
      ++segment_count;
      segment_start = i + 1;
    }
  }

  return segment_count == 5;
}

static void auth__base64url_32(
    const uint8 bytes[AUTH_CRYPTO_TOKEN_BYTES],
    char token[AUTH_CRYPTO_TOKEN_SIZE])
{
  static const char alphabet[] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
  size_t input_index = 0;
  size_t output_index = 0;

  while (input_index + 3 <= AUTH_CRYPTO_TOKEN_BYTES)
  {
    uint32 value = ((uint32)bytes[input_index] << 16) |
                   ((uint32)bytes[input_index + 1] << 8) |
                   bytes[input_index + 2];
    token[output_index++] = alphabet[(value >> 18) & 63];
    token[output_index++] = alphabet[(value >> 12) & 63];
    token[output_index++] = alphabet[(value >> 6) & 63];
    token[output_index++] = alphabet[value & 63];
    input_index += 3;
  }

  uint32 remainder = ((uint32)bytes[input_index] << 8) |
                     bytes[input_index + 1];
  token[output_index++] = alphabet[(remainder >> 10) & 63];
  token[output_index++] = alphabet[(remainder >> 4) & 63];
  token[output_index++] = alphabet[(remainder << 2) & 63];
  token[output_index] = '\0';
}

Auth_Crypto_Result Auth_Crypto_Password_Hash(
    const char *password,
    char *encoded_hash,
    size_t encoded_hash_capacity)
{
  if (!password || !encoded_hash)
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }
  if (encoded_hash_capacity < AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE)
  {
    return AUTH_CRYPTO_BUFFER_TOO_SMALL;
  }
  encoded_hash[0] = '\0';

  size_t password_length = 0;
  if (!auth__bounded_length(
          password, AUTH_CRYPTO_PASSWORD_MAX_BYTES, &password_length))
  {
    return AUTH_CRYPTO_PASSWORD_TOO_LONG;
  }

  uint8 salt[AUTH_CRYPTO_PASSWORD_SALT_BYTES];
  uint8 hash[AUTH_CRYPTO_PASSWORD_HASH_BYTES];
  if (RAND_bytes(salt, sizeof(salt)) != 1)
  {
    OPENSSL_cleanse(salt, sizeof(salt));
    return AUTH_CRYPTO_RANDOM_FAILED;
  }

  if (EVP_PBE_scrypt(
          password,
          password_length,
          salt,
          sizeof(salt),
          AUTH_CRYPTO_SCRYPT_N,
          AUTH_CRYPTO_SCRYPT_R,
          AUTH_CRYPTO_SCRYPT_P,
          AUTH_CRYPTO_SCRYPT_MAXMEM,
          hash,
          sizeof(hash)) != 1)
  {
    OPENSSL_cleanse(salt, sizeof(salt));
    OPENSSL_cleanse(hash, sizeof(hash));
    return AUTH_CRYPTO_OPERATION_FAILED;
  }

  char salt_hex[AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2 + 1];
  char hash_hex[AUTH_CRYPTO_PASSWORD_HASH_BYTES * 2 + 1];
  auth__hex_encode(salt, sizeof(salt), salt_hex);
  auth__hex_encode(hash, sizeof(hash), hash_hex);

  int written = snprintf(
      encoded_hash,
      encoded_hash_capacity,
      "%s%s$%s",
      AUTH_CRYPTO_PASSWORD_PREFIX,
      salt_hex,
      hash_hex);

  OPENSSL_cleanse(salt, sizeof(salt));
  OPENSSL_cleanse(hash, sizeof(hash));
  OPENSSL_cleanse(hash_hex, sizeof(hash_hex));

  if (written < 0 || (size_t)written >= encoded_hash_capacity)
  {
    encoded_hash[0] = '\0';
    return AUTH_CRYPTO_BUFFER_TOO_SMALL;
  }

  return AUTH_CRYPTO_OK;
}

Auth_Crypto_Result Auth_Crypto_Password_Verify(
    const char *password,
    const char *encoded_hash)
{
  if (!password || !encoded_hash)
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }

  size_t password_length = 0;
  if (!auth__bounded_length(
          password, AUTH_CRYPTO_PASSWORD_MAX_BYTES, &password_length))
  {
    return AUTH_CRYPTO_PASSWORD_TOO_LONG;
  }

  size_t encoded_length = 0;
  if (!auth__bounded_length(
          encoded_hash,
          AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE - 1,
          &encoded_length))
  {
    return AUTH_CRYPTO_MALFORMED;
  }

  size_t prefix_length = sizeof(AUTH_CRYPTO_PASSWORD_PREFIX) - 1;
  size_t expected_length = prefix_length +
                           AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2 + 1 +
                           AUTH_CRYPTO_PASSWORD_HASH_BYTES * 2;
  if (encoded_length != expected_length ||
      memcmp(encoded_hash, AUTH_CRYPTO_PASSWORD_PREFIX, prefix_length) != 0 ||
      encoded_hash[prefix_length + AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2] != '$')
  {
    return AUTH_CRYPTO_MALFORMED;
  }

  uint8 salt[AUTH_CRYPTO_PASSWORD_SALT_BYTES];
  uint8 expected_hash[AUTH_CRYPTO_PASSWORD_HASH_BYTES];
  uint8 calculated_hash[AUTH_CRYPTO_PASSWORD_HASH_BYTES];
  const char *salt_hex = encoded_hash + prefix_length;
  const char *hash_hex =
      salt_hex + AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2 + 1;

  if (!auth__hex_decode(
          salt_hex,
          AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2,
          salt,
          sizeof(salt)) ||
      !auth__hex_decode(
          hash_hex,
          AUTH_CRYPTO_PASSWORD_HASH_BYTES * 2,
          expected_hash,
          sizeof(expected_hash)))
  {
    return AUTH_CRYPTO_MALFORMED;
  }

  if (EVP_PBE_scrypt(
          password,
          password_length,
          salt,
          sizeof(salt),
          AUTH_CRYPTO_SCRYPT_N,
          AUTH_CRYPTO_SCRYPT_R,
          AUTH_CRYPTO_SCRYPT_P,
          AUTH_CRYPTO_SCRYPT_MAXMEM,
          calculated_hash,
          sizeof(calculated_hash)) != 1)
  {
    OPENSSL_cleanse(salt, sizeof(salt));
    OPENSSL_cleanse(expected_hash, sizeof(expected_hash));
    OPENSSL_cleanse(calculated_hash, sizeof(calculated_hash));
    return AUTH_CRYPTO_OPERATION_FAILED;
  }

  int comparison = CRYPTO_memcmp(
      calculated_hash, expected_hash, sizeof(calculated_hash));
  OPENSSL_cleanse(salt, sizeof(salt));
  OPENSSL_cleanse(expected_hash, sizeof(expected_hash));
  OPENSSL_cleanse(calculated_hash, sizeof(calculated_hash));

  return comparison == 0
      ? AUTH_CRYPTO_OK
      : AUTH_CRYPTO_AUTHENTICATION_FAILED;
}

Auth_Crypto_Result Auth_Crypto_Password_Hash_Validate(const char *encoded_hash)
{
  if (!encoded_hash)
    return AUTH_CRYPTO_INVALID_ARGUMENT;

  size_t encoded_length = 0;
  if (!auth__bounded_length(
          encoded_hash,
          AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE - 1,
          &encoded_length))
    return AUTH_CRYPTO_MALFORMED;

  size_t prefix_length = sizeof(AUTH_CRYPTO_PASSWORD_PREFIX) - 1;
  size_t expected_length = prefix_length +
                           AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2 + 1 +
                           AUTH_CRYPTO_PASSWORD_HASH_BYTES * 2;
  if (encoded_length != expected_length ||
      memcmp(encoded_hash, AUTH_CRYPTO_PASSWORD_PREFIX, prefix_length) != 0 ||
      encoded_hash[prefix_length + AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2] != '$')
    return AUTH_CRYPTO_MALFORMED;

  /* Validate hex characters in salt and hash fields. */
  const char *salt_hex = encoded_hash + prefix_length;
  const char *hash_hex = salt_hex + AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2 + 1;
  for (size_t i = 0; i < AUTH_CRYPTO_PASSWORD_SALT_BYTES * 2; i++)
    if (auth__hex_value(salt_hex[i]) < 0)
      return AUTH_CRYPTO_MALFORMED;
  for (size_t i = 0; i < AUTH_CRYPTO_PASSWORD_HASH_BYTES * 2; i++)
    if (auth__hex_value(hash_hex[i]) < 0)
      return AUTH_CRYPTO_MALFORMED;

  return AUTH_CRYPTO_OK;
}

Auth_Crypto_Result Auth_Crypto_Token_Generate(
    char *token,
    size_t token_capacity)
{
  if (!token)
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }
  if (token_capacity < AUTH_CRYPTO_TOKEN_SIZE)
  {
    return AUTH_CRYPTO_BUFFER_TOO_SMALL;
  }
  token[0] = '\0';

  uint8 random_bytes[AUTH_CRYPTO_TOKEN_BYTES];
  if (RAND_bytes(random_bytes, sizeof(random_bytes)) != 1)
  {
    OPENSSL_cleanse(random_bytes, sizeof(random_bytes));
    return AUTH_CRYPTO_RANDOM_FAILED;
  }

  auth__base64url_32(random_bytes, token);
  OPENSSL_cleanse(random_bytes, sizeof(random_bytes));
  return AUTH_CRYPTO_OK;
}

Auth_Crypto_Result Auth_Crypto_Token_Digest(
    const char *token,
    char *digest_hex,
    size_t digest_hex_capacity)
{
  if (!token || !digest_hex)
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }
  if (digest_hex_capacity < AUTH_CRYPTO_TOKEN_DIGEST_SIZE)
  {
    return AUTH_CRYPTO_BUFFER_TOO_SMALL;
  }
  digest_hex[0] = '\0';

  size_t token_length = 0;
  if (!auth__bounded_length(token, AUTH_CRYPTO_TOKEN_SIZE - 1, &token_length) ||
      token_length != AUTH_CRYPTO_TOKEN_SIZE - 1)
  {
    return AUTH_CRYPTO_MALFORMED;
  }
  for (size_t i = 0; i < token_length; ++i)
  {
    char value = token[i];
    if (!((value >= 'A' && value <= 'Z') ||
          (value >= 'a' && value <= 'z') ||
          (value >= '0' && value <= '9') ||
          value == '-' || value == '_'))
    {
      return AUTH_CRYPTO_MALFORMED;
    }
  }

  uint8 digest[AUTH_CRYPTO_TOKEN_DIGEST_BYTES];
  unsigned int digest_length = 0;
  if (EVP_Digest(
          token,
          token_length,
          digest,
          &digest_length,
          EVP_sha256(),
          NULL) != 1 ||
      digest_length != AUTH_CRYPTO_TOKEN_DIGEST_BYTES)
  {
    OPENSSL_cleanse(digest, sizeof(digest));
    return AUTH_CRYPTO_OPERATION_FAILED;
  }

  auth__hex_encode(digest, sizeof(digest), digest_hex);
  OPENSSL_cleanse(digest, sizeof(digest));
  return AUTH_CRYPTO_OK;
}

Auth_Crypto_Result Auth_Crypto_IP_Binding_Digest(
    const uint8 *cookie_secret,
    size_t cookie_secret_length,
    const char *canonical_peer_ip,
    char *digest_hex,
    size_t digest_hex_capacity)
{
  if (!auth__secret_is_valid(cookie_secret, cookie_secret_length) ||
      !canonical_peer_ip || !digest_hex)
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }
  if (digest_hex_capacity < AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE)
  {
    return AUTH_CRYPTO_BUFFER_TOO_SMALL;
  }
  digest_hex[0] = '\0';

  size_t ip_length = 0;
  if (!auth__bounded_length(
          canonical_peer_ip, AUTH_CRYPTO_IP_MAX_BYTES, &ip_length) ||
      ip_length == 0)
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }

  uint8 digest[32];
  Auth_Crypto_Result result = auth__hmac_sha256(
      cookie_secret,
      cookie_secret_length,
      AUTH_CRYPTO_IP_HMAC_DOMAIN,
      canonical_peer_ip,
      ip_length,
      digest);
  if (result != AUTH_CRYPTO_OK)
  {
    return result;
  }

  auth__hex_encode(digest, sizeof(digest), digest_hex);
  OPENSSL_cleanse(digest, sizeof(digest));
  return AUTH_CRYPTO_OK;
}

Auth_Crypto_Result Auth_Crypto_Guest_Cookie_Create(
    const uint8 *cookie_secret,
    size_t cookie_secret_length,
    const char *guest_uuid,
    uint64 expiration_unix,
    const char *ip_binding_digest,
    char *cookie,
    size_t cookie_capacity)
{
  if (!auth__secret_is_valid(cookie_secret, cookie_secret_length) ||
      !guest_uuid || !ip_binding_digest || !cookie ||
      expiration_unix == 0)
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }
  if (cookie_capacity < AUTH_CRYPTO_GUEST_COOKIE_SIZE)
  {
    return AUTH_CRYPTO_BUFFER_TOO_SMALL;
  }
  cookie[0] = '\0';

  size_t uuid_length = 0;
  if (!auth__bounded_length(
          guest_uuid, AUTH_CRYPTO_GUEST_UUID_SIZE - 1, &uuid_length) ||
      !auth__uuid_is_valid(guest_uuid, uuid_length))
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }

  size_t ip_digest_length = 0;
  uint8 ip_digest_bytes[32];
  if (!auth__bounded_length(
          ip_binding_digest,
          AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE - 1,
          &ip_digest_length) ||
      !auth__hex_decode(
          ip_binding_digest,
          ip_digest_length,
          ip_digest_bytes,
          sizeof(ip_digest_bytes)))
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }
  OPENSSL_cleanse(ip_digest_bytes, sizeof(ip_digest_bytes));

  char payload[AUTH_CRYPTO_GUEST_COOKIE_SIZE];
  int payload_written = snprintf(
      payload,
      sizeof(payload),
      "%s.%s.%llu.%s",
      AUTH_CRYPTO_COOKIE_VERSION,
      guest_uuid,
      (unsigned long long)expiration_unix,
      ip_binding_digest);
  if (payload_written < 0 || (size_t)payload_written >= sizeof(payload))
  {
    return AUTH_CRYPTO_OPERATION_FAILED;
  }

  uint8 signature[32];
  Auth_Crypto_Result result = auth__hmac_sha256(
      cookie_secret,
      cookie_secret_length,
      AUTH_CRYPTO_COOKIE_HMAC_DOMAIN,
      payload,
      (size_t)payload_written,
      signature);
  if (result != AUTH_CRYPTO_OK)
  {
    return result;
  }

  char signature_hex[65];
  auth__hex_encode(signature, sizeof(signature), signature_hex);
  OPENSSL_cleanse(signature, sizeof(signature));

  int cookie_written = snprintf(
      cookie,
      cookie_capacity,
      "%s.%s",
      payload,
      signature_hex);
  OPENSSL_cleanse(signature_hex, sizeof(signature_hex));
  if (cookie_written < 0 || (size_t)cookie_written >= cookie_capacity)
  {
    cookie[0] = '\0';
    return AUTH_CRYPTO_BUFFER_TOO_SMALL;
  }

  return AUTH_CRYPTO_OK;
}

Auth_Crypto_Result Auth_Crypto_Guest_Cookie_Verify(
    const uint8 *cookie_secret,
    size_t cookie_secret_length,
    const char *cookie,
    uint64 current_unix,
    const char *expected_ip_binding_digest,
    Auth_Crypto_Guest_Cookie *guest_cookie)
{
  if (!auth__secret_is_valid(cookie_secret, cookie_secret_length) ||
      !cookie || !expected_ip_binding_digest || !guest_cookie)
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }
  memset(guest_cookie, 0, sizeof(*guest_cookie));

  size_t expected_ip_length = 0;
  uint8 expected_ip[32];
  if (!auth__bounded_length(
          expected_ip_binding_digest,
          AUTH_CRYPTO_IP_BINDING_DIGEST_SIZE - 1,
          &expected_ip_length) ||
      !auth__hex_decode(
          expected_ip_binding_digest,
          expected_ip_length,
          expected_ip,
          sizeof(expected_ip)))
  {
    return AUTH_CRYPTO_INVALID_ARGUMENT;
  }

  size_t cookie_length = 0;
  if (!auth__bounded_length(
          cookie, AUTH_CRYPTO_GUEST_COOKIE_SIZE - 1, &cookie_length))
  {
    OPENSSL_cleanse(expected_ip, sizeof(expected_ip));
    return AUTH_CRYPTO_MALFORMED;
  }

  const char *segments[5];
  size_t segment_lengths[5];
  if (!auth__split_cookie(
          cookie, cookie_length, segments, segment_lengths) ||
      segment_lengths[0] != sizeof(AUTH_CRYPTO_COOKIE_VERSION) - 1 ||
      memcmp(
          segments[0],
          AUTH_CRYPTO_COOKIE_VERSION,
          sizeof(AUTH_CRYPTO_COOKIE_VERSION) - 1) != 0 ||
      !auth__uuid_is_valid(segments[1], segment_lengths[1]))
  {
    OPENSSL_cleanse(expected_ip, sizeof(expected_ip));
    return AUTH_CRYPTO_MALFORMED;
  }

  uint64 expiration_unix = 0;
  uint8 cookie_ip[32];
  uint8 provided_signature[32];
  if (!auth__parse_uint64(
          segments[2], segment_lengths[2], &expiration_unix) ||
      expiration_unix == 0 ||
      !auth__hex_decode(
          segments[3],
          segment_lengths[3],
          cookie_ip,
          sizeof(cookie_ip)) ||
      !auth__hex_decode(
          segments[4],
          segment_lengths[4],
          provided_signature,
          sizeof(provided_signature)))
  {
    OPENSSL_cleanse(expected_ip, sizeof(expected_ip));
    return AUTH_CRYPTO_MALFORMED;
  }

  size_t payload_length = (size_t)(segments[4] - cookie - 1);
  uint8 calculated_signature[32];
  Auth_Crypto_Result result = auth__hmac_sha256(
      cookie_secret,
      cookie_secret_length,
      AUTH_CRYPTO_COOKIE_HMAC_DOMAIN,
      cookie,
      payload_length,
      calculated_signature);
  if (result != AUTH_CRYPTO_OK)
  {
    OPENSSL_cleanse(expected_ip, sizeof(expected_ip));
    OPENSSL_cleanse(cookie_ip, sizeof(cookie_ip));
    OPENSSL_cleanse(provided_signature, sizeof(provided_signature));
    return result;
  }

  int signature_comparison = CRYPTO_memcmp(
      calculated_signature,
      provided_signature,
      sizeof(calculated_signature));
  int ip_comparison = CRYPTO_memcmp(
      cookie_ip,
      expected_ip,
      sizeof(cookie_ip));
  OPENSSL_cleanse(calculated_signature, sizeof(calculated_signature));
  OPENSSL_cleanse(provided_signature, sizeof(provided_signature));
  OPENSSL_cleanse(cookie_ip, sizeof(cookie_ip));
  OPENSSL_cleanse(expected_ip, sizeof(expected_ip));

  if (signature_comparison != 0)
  {
    return AUTH_CRYPTO_AUTHENTICATION_FAILED;
  }
  if (ip_comparison != 0)
  {
    return AUTH_CRYPTO_IP_MISMATCH;
  }
  if (current_unix >= expiration_unix)
  {
    return AUTH_CRYPTO_EXPIRED;
  }

  memcpy(
      guest_cookie->guest_uuid,
      segments[1],
      AUTH_CRYPTO_GUEST_UUID_SIZE - 1);
  guest_cookie->guest_uuid[AUTH_CRYPTO_GUEST_UUID_SIZE - 1] = '\0';
  guest_cookie->expiration_unix = expiration_unix;
  return AUTH_CRYPTO_OK;
}

const char *Auth_Crypto_Result_String(Auth_Crypto_Result result)
{
  switch (result)
  {
    case AUTH_CRYPTO_OK:
      return "ok";
    case AUTH_CRYPTO_INVALID_ARGUMENT:
      return "invalid argument";
    case AUTH_CRYPTO_BUFFER_TOO_SMALL:
      return "buffer too small";
    case AUTH_CRYPTO_PASSWORD_TOO_LONG:
      return "password too long";
    case AUTH_CRYPTO_RANDOM_FAILED:
      return "secure random generation failed";
    case AUTH_CRYPTO_OPERATION_FAILED:
      return "cryptographic operation failed";
    case AUTH_CRYPTO_MALFORMED:
      return "malformed input";
    case AUTH_CRYPTO_AUTHENTICATION_FAILED:
      return "authentication failed";
    case AUTH_CRYPTO_EXPIRED:
      return "expired";
    case AUTH_CRYPTO_IP_MISMATCH:
      return "IP binding mismatch";
  }

  return "unknown result";
}

size_t Auth_Crypto_Base64url_Encode(
    const uint8 *bytes,
    size_t       byte_count,
    char        *out,
    size_t       out_capacity)
{
  if (!bytes || !out || out_capacity == 0)
    return 0;

  /* Unpadded output length: full groups + partial group chars */
  size_t full_groups   = byte_count / 3;
  size_t remainder     = byte_count % 3;
  size_t unpadded_len  = full_groups * 4 +
                         (remainder == 0 ? 0 : remainder + 1);
  if (out_capacity < unpadded_len + 1)
  {
    out[0] = '\0';
    return 0;
  }

  static const char kAlpha[] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

  size_t in_idx = 0, out_idx = 0;

  /* Full 3-byte groups */
  while (in_idx + 2 < byte_count)
  {
    uint32 v = ((uint32)bytes[in_idx]     << 16) |
               ((uint32)bytes[in_idx + 1] <<  8) |
                        bytes[in_idx + 2];
    out[out_idx++] = kAlpha[(v >> 18) & 0x3f];
    out[out_idx++] = kAlpha[(v >> 12) & 0x3f];
    out[out_idx++] = kAlpha[(v >>  6) & 0x3f];
    out[out_idx++] = kAlpha[(v      ) & 0x3f];
    in_idx += 3;
  }

  /* Remaining 1 or 2 bytes (no padding) */
  if (in_idx < byte_count)
  {
    uint32 v = (uint32)bytes[in_idx] << 16;
    if (in_idx + 1 < byte_count)
      v |= (uint32)bytes[in_idx + 1] << 8;
    out[out_idx++] = kAlpha[(v >> 18) & 0x3f];
    out[out_idx++] = kAlpha[(v >> 12) & 0x3f];
    if (in_idx + 1 < byte_count)
      out[out_idx++] = kAlpha[(v >> 6) & 0x3f];
  }

  out[out_idx] = '\0';
  return out_idx;
}