view auth/auth_crypto.c @ 268:f7b1188d5fb1

support repo rules Copilot bundle path Match both +_repo_rules2+ and +http_archive+ Copilot CLI repository layouts during deployment and production startup. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 13:02:41 -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;
}