std1.0.0-beta-7 SafeC docs

Source crypto/rng.sc

View in the repository

// SafeC Standard Library — Cryptographic RNG (ChaCha20-based)
// safeguard: link windows -lbcrypt          (BCryptGenRandom: the seed on Windows)
#pragma once
#include <std/stddef.h>
#include <std/crypto/rng.h>
#include <std/stderr_compat.h>

namespace std {

extern unsigned char* memcpy(unsigned char* d, const unsigned char* s, size_t n);
// Hosted-mode seeding from /dev/urandom. Declared here at file scope: this
// parser only accepts 'extern' declarations at top level, not nested inside
// a function body.
extern CFile_* fopen(const char* path, const char* mode);
extern size_t fread(unsigned char* buf, size_t size, size_t n, CFile_* f);
extern int fclose(CFile_* f);
// Same rationale as mem.sc/pool.sc/slab.sc/tlsf.sc for not using
// panic.h/panic_at here (no forced --compat-preprocessor link dependency
// for every RNG user).
extern void  abort();
extern int   fprintf(CFile_* stream, const char* fmt, ...);
// The OS's own CSPRNG, the first seed source tried (see RngCtx::init).
#if defined(_WIN32)
// bcrypt.dll: NTSTATUS BCryptGenRandom(BCRYPT_ALG_HANDLE, PUCHAR, ULONG, ULONG);
// a null algorithm with BCRYPT_USE_SYSTEM_PREFERRED_RNG (2) needs no handle.
struct BCryptAlg_ { char opaque_; };   // BCRYPT_ALG_HANDLE (null: the system RNG)
extern int BCryptGenRandom(BCryptAlg_* alg, unsigned char* buf, unsigned int len, unsigned int flags);
#elif defined(__APPLE__)
extern int getentropy(unsigned char* buf, size_t len);              // libSystem, at most 256 bytes
#elif defined(__linux__)
extern long getrandom(unsigned char* buf, size_t len, unsigned int flags);   // glibc 2.25+, musl
#endif

static void rng_abort_(const char* msg) {
    unsafe { fprintf(SAFEC_STDERR_, "std::crypto::rng fatal: %s\n", msg); }
    unsafe { abort(); }
}

// ── ChaCha20 core ─────────────────────────────────────────────────────────────

static unsigned int chacha_rotl_(unsigned int v, int n) {
    return (v << n) | (v >> (32 - n));
}

static void chacha_qr_(unsigned int* a, unsigned int* b,
                        unsigned int* c, unsigned int* d) {
    unsafe {
        *a = *a + *b; *d = chacha_rotl_(*d ^ *a, 16);
        *c = *c + *d; *b = chacha_rotl_(*b ^ *c, 12);
        *a = *a + *b; *d = chacha_rotl_(*d ^ *a,  8);
        *c = *c + *d; *b = chacha_rotl_(*b ^ *c,  7);
    }
}

// Produce 64 bytes of keystream into `out` from state `s`.
static void chacha20_block_(const unsigned int* s, unsigned char* out) {
    unsigned int x[16];
    int i = 0;
    unsafe { while (i < 16) { x[i] = s[i]; i = i + 1; } }

    // 10 double-rounds
    int r = 0;
    while (r < 10) {
        unsafe {
            chacha_qr_((unsigned int*)&x[ 0],(unsigned int*)&x[ 4],(unsigned int*)&x[ 8],(unsigned int*)&x[12]);
            chacha_qr_((unsigned int*)&x[ 1],(unsigned int*)&x[ 5],(unsigned int*)&x[ 9],(unsigned int*)&x[13]);
            chacha_qr_((unsigned int*)&x[ 2],(unsigned int*)&x[ 6],(unsigned int*)&x[10],(unsigned int*)&x[14]);
            chacha_qr_((unsigned int*)&x[ 3],(unsigned int*)&x[ 7],(unsigned int*)&x[11],(unsigned int*)&x[15]);
            chacha_qr_((unsigned int*)&x[ 0],(unsigned int*)&x[ 5],(unsigned int*)&x[10],(unsigned int*)&x[15]);
            chacha_qr_((unsigned int*)&x[ 1],(unsigned int*)&x[ 6],(unsigned int*)&x[11],(unsigned int*)&x[12]);
            chacha_qr_((unsigned int*)&x[ 2],(unsigned int*)&x[ 7],(unsigned int*)&x[ 8],(unsigned int*)&x[13]);
            chacha_qr_((unsigned int*)&x[ 3],(unsigned int*)&x[ 4],(unsigned int*)&x[ 9],(unsigned int*)&x[14]);
        }
        r = r + 1;
    }

    i = 0;
    unsafe { while (i < 16) { x[i] = x[i] + s[i]; i = i + 1; } }

    // Serialise little-endian
    unsafe {
        i = 0;
        while (i < 16) {
            out[i*4+0] = (unsigned char)( x[i]       & 0xFF);
            out[i*4+1] = (unsigned char)((x[i] >>  8) & 0xFF);
            out[i*4+2] = (unsigned char)((x[i] >> 16) & 0xFF);
            out[i*4+3] = (unsigned char)((x[i] >> 24) & 0xFF);
            i = i + 1;
        }
    }
}

// ── Initialisation ─────────────────────────────────────────────────────────────

// ChaCha20 "expa nd 32-byte k" constant
static unsigned int chacha_const_[4] = {
    0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
};

static void rng_setup_(unsigned int* state, const unsigned char* key32,
                        unsigned int ctr, unsigned int nonce) {
    int i = 0;
    unsafe { while (i < 4) { state[i] = chacha_const_[i]; i = i + 1; } }
    unsafe {
        i = 0;
        while (i < 8) {
            state[4+i] = ((unsigned int)key32[i*4+0])
                       | ((unsigned int)key32[i*4+1] << 8)
                       | ((unsigned int)key32[i*4+2] << 16)
                       | ((unsigned int)key32[i*4+3] << 24);
            i = i + 1;
        }
    }
    unsafe {
        state[12] = ctr;
        state[13] = (unsigned int)0;
        state[14] = nonce;
        state[15] = (unsigned int)0;
    }
}

inline void RngCtx::init_seed(const unsigned char seed[32]) {
    rng_setup_(self.state, seed, (unsigned int)0, (unsigned int)1);
    chacha20_block_(self.state, (unsigned char*)self.buf);
    self.state[12] = self.state[12] + (unsigned int)1;
    self.buf_pos = (unsigned long)0;
}

int RngCtx::init() {
    // The OS's CSPRNG first: BCryptGenRandom on Windows (where there is no
    // /dev/urandom), getentropy on macOS, getrandom on Linux.
#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__)
    unsafe {
        unsigned char os_seed[32];
        int os_ok = 0;
#if defined(_WIN32)
        if (BCryptGenRandom((BCryptAlg_*)0, (unsigned char*)os_seed, (unsigned int)32, (unsigned int)2) == 0) { os_ok = 1; }
#elif defined(__APPLE__)
        if (getentropy((unsigned char*)os_seed, (size_t)32) == 0) { os_ok = 1; }
#else
        if (getrandom((unsigned char*)os_seed, (size_t)32, (unsigned int)0) == (long)32) { os_ok = 1; }
#endif
        if (os_ok != 0) {
            self.init_seed(os_seed);
            return 1;
        }
    }
#endif

    // Else rdrand (x86-64), else /dev/urandom (a Linux kernel without
    // getrandom, the BSDs).
#ifdef __x86_64__
    // Try rdrand for seeding
    unsigned int seed_words[8];
    int ok = 1;
    unsafe {
        int i = 0;
        while (i < 8) {
            unsigned int val = (unsigned int)0;
            int success = 0;
            int tries = 0;
            while (tries < 10 && success == 0) {
                asm volatile (
                    "xorl %0, %0\n\t"
                    "rdrand %1\n\t"
                    "setc %b0"
                    : "=r"(success), "=r"(val) : : "cc"
                );
                tries = tries + 1;
            }
            if (success == 0) { ok = 0; }
            seed_words[i] = val;
            i = i + 1;
        }
    }
    if (ok != 0) {
        rng_setup_(self.state, (const unsigned char*)seed_words, (unsigned int)0, (unsigned int)1);
        chacha20_block_(self.state, (unsigned char*)self.buf);
        self.state[12] = self.state[12] + (unsigned int)1;
        self.buf_pos = (unsigned long)0;
        return 1;
    }
#endif

#ifndef __SAFEC_FREESTANDING__
    // Hosted: seed from /dev/urandom
    unsafe {
        CFile_* f = fopen("/dev/urandom", "rb");
        if (f != (CFile_*)0) {
            unsigned char seed[32];
            unsigned long got = fread((unsigned char*)seed, (unsigned long)1, (unsigned long)32, f);
            fclose(f);
            if (got == (unsigned long)32) {      // a short read would seed from stack garbage
                self.init_seed(seed);
                return 1;
            }
        }
    }
#endif

    // No real entropy source was available (no OS CSPRNG, no rdrand, no
    // /dev/urandom — e.g. a freestanding target without either). Previously this silently
    // fell back to a fixed, fully-predictable seed and returned 0, trusting
    // every caller to check that return value before treating the "random"
    // stream as safe to use for keys/nonces — nothing enforced that, so a
    // caller that (reasonably) assumed init() always produces usable
    // randomness would get deterministic output with no warning. Aborting
    // here instead makes the failure impossible to miss. Callers that
    // genuinely have their own entropy source (e.g. a hardware TRNG driver
    // on a microcontroller with no rdrand/urandom) should call
    // init_seed() directly with that source instead of init().
    rng_abort_("RngCtx::init() found no entropy source (no OS random source, no rdrand, no /dev/urandom) — "
               "refusing to seed with a predictable fallback; call RngCtx::init_seed() "
               "with your own entropy source instead");
    return 0;
}

// ── RngCtx methods ─────────────────────────────────────────────────────────────

inline void RngCtx::fill_n_(&unsigned char out, unsigned long len) {
    unsigned long i = (unsigned long)0;
    while (i < len) {
        if (self.buf_pos >= (unsigned long)RNG_BLOCK_SIZE) {
            chacha20_block_(self.state, (unsigned char*)self.buf);
            self.state[12] = self.state[12] + (unsigned int)1;
            self.buf_pos = (unsigned long)0;
        }
        unsafe { out[i] = self.buf[self.buf_pos]; }
        self.buf_pos = self.buf_pos + (unsigned long)1;
        i = i + (unsigned long)1;
    }
}

inline void RngCtx::fill([]unsigned char out_s) {
    unsigned long len = out_s.len;
    &unsigned char out = crypto_out_(out_s, 0UL);
    self.fill_n_(out, len);
}

inline unsigned int RngCtx::rand32() {
    unsigned char b[4];
    self.fill_n_(&b[0], 4UL);
    unsafe {
        return ((unsigned int)b[0])
             | ((unsigned int)b[1] << 8)
             | ((unsigned int)b[2] << 16)
             | ((unsigned int)b[3] << 24);
    }
}

inline unsigned long long RngCtx::rand64() {
    unsigned long long lo = (unsigned long long)self.rand32();
    unsigned long long hi = (unsigned long long)self.rand32();
    return (hi << 32ULL) | lo;
}

inline unsigned long RngCtx::rand_range(unsigned long bound) {
    if (bound <= (unsigned long)1) { return (unsigned long)0; }
    // Rejection sampling to eliminate bias
    unsigned long threshold = (~bound + (unsigned long)1) % bound;
    unsafe {
        while (1) {
            unsigned long r = (unsigned long)self.rand64();   // 32 bits on 32-bit targets: threshold matches
            if (r >= threshold) { return r % bound; }
        }
    }
    return (unsigned long)0;
}

} // namespace std