packages feed

KpqC-0.1.0: cbits/native/common/randombytes.h

/* SPDX-License-Identifier: MIT */
#include <stddef.h>
#include <stdint.h>
#if defined(KPQC_TEST_ENTROPY)
#include <string.h>
#include <stdlib.h>
#endif

#if defined(_WIN32)
#include <limits.h>
#include <windows.h>
#include <bcrypt.h>
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
    defined(__NetBSD__) || defined(__DragonFly__)
#include <stdlib.h>
#elif defined(__linux__) || defined(__ANDROID__)
#include <errno.h>
#include <sys/random.h>
#else
#error "kpqc supports operating-system entropy on Windows, Linux, Android, macOS, and BSD"
#endif

#if defined(KPQC_TEST_ENTROPY)
/*
 * This override is compiled only for kpqc_kat builds and is exposed through
 * internal/native. It lets the KAT tests reproduce the NIST CTR-DRBG stream
 * without changing the public API or the operating-system entropy used in
 * normal builds.
 */
static uint8_t *kpqc_test_entropy = NULL;
static size_t kpqc_test_entropy_length = 0;
static size_t kpqc_test_entropy_offset = 0;
static int kpqc_test_entropy_active = 0;

static void kpqc_clear_test_entropy(void) {
    if (kpqc_test_entropy != NULL) {
        volatile uint8_t *bytes = kpqc_test_entropy;
        for (size_t i = 0; i < kpqc_test_entropy_length; ++i) bytes[i] = 0;
        free(kpqc_test_entropy);
    }
    kpqc_test_entropy = NULL;
    kpqc_test_entropy_length = 0;
    kpqc_test_entropy_offset = 0;
    kpqc_test_entropy_active = 0;
}

static int kpqc_set_test_entropy(const uint8_t *input, size_t length) {
    kpqc_clear_test_entropy();
    if (length != 0) {
        kpqc_test_entropy = (uint8_t *)malloc(length);
        if (kpqc_test_entropy == NULL) return -1;
        memcpy(kpqc_test_entropy, input, length);
    }
    kpqc_test_entropy_length = length;
    kpqc_test_entropy_active = 1;
    return 0;
}

static size_t kpqc_test_entropy_remaining(void) {
    return kpqc_test_entropy_length - kpqc_test_entropy_offset;
}
#endif

static int kpqc_fill_random(uint8_t *output, size_t length) {
#if defined(KPQC_TEST_ENTROPY)
    if (kpqc_test_entropy_active) {
        if (length > kpqc_test_entropy_remaining()) return -1;
        if (length != 0) {
            memcpy(output, kpqc_test_entropy + kpqc_test_entropy_offset, length);
            kpqc_test_entropy_offset += length;
        }
        return 0;
    }
#endif
#if defined(_WIN32)
    while (length != 0) {
        ULONG chunk = length > (size_t)ULONG_MAX ? ULONG_MAX : (ULONG)length;
        if (BCryptGenRandom(NULL, output, chunk,
                            BCRYPT_USE_SYSTEM_PREFERRED_RNG) != 0) {
            return -1;
        }
        output += chunk;
        length -= chunk;
    }
    return 0;
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
    defined(__NetBSD__) || defined(__DragonFly__)
    arc4random_buf(output, length);
    return 0;
#else
    while (length != 0) {
        ssize_t count = getrandom(output, length, 0);
        if (count < 0) {
            if (errno == EINTR) continue;
            return -1;
        }
        if (count == 0) return -1;
        output += (size_t)count;
        length -= (size_t)count;
    }
    return 0;
#endif
}

#if defined(KPQC_AIMER)
int randombytes(unsigned char *output, unsigned long long length) {
    if (length > (unsigned long long)SIZE_MAX) return -1;
    return kpqc_fill_random(output, (size_t)length);
}
#else
int randombytes(uint8_t *output, size_t length) {
    return kpqc_fill_random(output, length);
}
#endif