Viewing: csprng.c
📄 csprng.c (Read Only) ⬅ To go back
// SPDX-License-Identifier: BSD-3-Clause
/*
 * Copyright (c) 2018, Tulio A M Mendes <[email protected]>
 * All rights reserved.
 * See LICENSE for details.
 *
 * Source: https://github.com/tadryanom/AdrOS
 */

#include "csprng.h"
#include "spinlock.h"
#include "timer.h"
#include "utils.h"

#include <stddef.h>

/* M8: Central CSPRNG for kernel cryptographic randomness
 * Uses a ChaCha20-based DRBG with entropy accumulation from:
 * - RDTSC (high-resolution timer)
 * - Timer tick count
 * - Interrupt timing variations
 * - User-provided entropy via /dev/random writes
 */

static spinlock_t g_csprng_lock = {0};

/* ChaCha20 state (simplified for DRBG use) */
static struct {
    uint32_t state[16];
    uint32_t counter;
    uint8_t  initialized;
} g_csprng_chacha;

/* Entropy pool */
static struct {
    uint8_t pool[64];
    uint32_t pool_pos;
    uint32_t reseed_counter;
} g_entropy_pool;

/* Rotate left macro */
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))

/* ChaCha20 quarter round */
static void chacha_quarter_round(uint32_t* a, uint32_t* b, uint32_t* c, uint32_t* d) {
    *a += *b; *d ^= *a; *d = ROTL32(*d, 16);
    *c += *d; *b ^= *c; *b = ROTL32(*b, 12);
    *a += *b; *d ^= *a; *d = ROTL32(*d, 8);
    *c += *d; *b ^= *c; *b = ROTL32(*b, 7);
}

/* ChaCha20 block function (single round for DRBG) */
static void chacha20_block(uint32_t* state) {
    for (int i = 0; i < 10; i++) {
        /* Column rounds */
        chacha_quarter_round(&state[0], &state[4], &state[8],  &state[12]);
        chacha_quarter_round(&state[1], &state[5], &state[9],  &state[13]);
        chacha_quarter_round(&state[2], &state[6], &state[10], &state[14]);
        chacha_quarter_round(&state[3], &state[7], &state[11], &state[15]);
        
        /* Diagonal rounds */
        chacha_quarter_round(&state[0], &state[5], &state[10], &state[15]);
        chacha_quarter_round(&state[1], &state[6], &state[11], &state[12]);
        chacha_quarter_round(&state[2], &state[7], &state[8],  &state[13]);
        chacha_quarter_round(&state[3], &state[4], &state[9],  &state[14]);
    }
}

/* Read RDTSC for entropy */
static uint64_t rdtsc_entropy(void) {
    uint64_t tsc = 0;
    /* Inline RDTSC */
    __asm__ volatile("rdtsc" : "=A"(tsc));
    return tsc;
}

/* Mix entropy into pool */
static void entropy_mix(const uint8_t* data, uint32_t len) {
    uintptr_t irqf = spin_lock_irqsave(&g_csprng_lock);
    
    for (uint32_t i = 0; i < len; i++) {
        g_entropy_pool.pool[g_entropy_pool.pool_pos] ^= data[i];
        g_entropy_pool.pool_pos = (g_entropy_pool.pool_pos + 1) % 64;
    }
    
    spin_unlock_irqrestore(&g_csprng_lock, irqf);
}

/* Initialize CSPRNG with boot entropy */
void csprng_init(void) {
    uintptr_t irqf = spin_lock_irqsave(&g_csprng_lock);
    
    /* Seed with multiple entropy sources */
    uint64_t tsc = rdtsc_entropy();
    uint32_t ticks = get_tick_count();
    
    /* Initialize ChaCha20 state with "expand 32-byte k" constant */
    const char* constant = "expand 32-byte k";
    for (int i = 0; i < 4; i++) {
        g_csprng_chacha.state[i] = ((uint32_t)constant[i*4+0]) << 0 |
                                   ((uint32_t)constant[i*4+1]) << 8 |
                                   ((uint32_t)constant[i*4+2]) << 16 |
                                   ((uint32_t)constant[i*4+3]) << 24;
    }
    
    /* Mix in entropy */
    for (int i = 4; i < 16; i++) {
        uint64_t mix = tsc ^ ((uint64_t)ticks << 32);
        mix += i * 0x9E3779B9; /* Golden ratio */
        g_csprng_chacha.state[i] = (uint32_t)(mix ^ (mix >> 32));
        tsc ^= (mix << 13) | (mix >> 51);
    }
    
    g_csprng_chacha.counter = 1;
    g_csprng_chacha.initialized = 1;

    /* Initialize entropy pool with TSC bytes (8 bytes) */
    for (int i = 0; i < 8; i++) {
        g_entropy_pool.pool[i] = (uint8_t)(tsc >> (i * 8));
    }
    /* Fill remaining pool with tick count variations */
    for (int i = 8; i < 64; i++) {
        g_entropy_pool.pool[i] = (uint8_t)(ticks ^ (i * 0x9E3779B9));
    }
    g_entropy_pool.pool_pos = 0;
    g_entropy_pool.reseed_counter = 0;
    
    spin_unlock_irqrestore(&g_csprng_lock, irqf);
}

/* Add entropy to CSPRNG (for /dev/random writes) */
void csprng_add_entropy(const uint8_t* data, uint32_t len) {
    if (!data || len == 0) return;
    
    entropy_mix(data, len);
    
    uintptr_t irqf = spin_lock_irqsave(&g_csprng_lock);
    g_entropy_pool.reseed_counter++;
    
    /* Reseed every 256 entropy additions */
    if (g_entropy_pool.reseed_counter >= 256) {
        /* Mix entropy pool into ChaCha20 state */
        for (int i = 0; i < 16; i++) {
            uint32_t pool_word = 0;
            for (int j = 0; j < 4; j++) {
                pool_word |= ((uint32_t)g_entropy_pool.pool[(i*4 + j) % 64]) << (j * 8);
            }
            g_csprng_chacha.state[i] ^= pool_word;
        }
        g_entropy_pool.reseed_counter = 0;
    }
    
    spin_unlock_irqrestore(&g_csprng_lock, irqf);
}

/* Generate random bytes */
void csprng_get_bytes(uint8_t* out, uint32_t len) {
    if (!out || len == 0) return;
    
    uintptr_t irqf = spin_lock_irqsave(&g_csprng_lock);
    
    if (!g_csprng_chacha.initialized) {
        spin_unlock_irqrestore(&g_csprng_lock, irqf);
        csprng_init();
        irqf = spin_lock_irqsave(&g_csprng_lock);
    }
    
    /* Add timing entropy before generation */
    uint64_t tsc = rdtsc_entropy();
    g_csprng_chacha.state[12] ^= (uint32_t)tsc;
    g_csprng_chacha.state[13] ^= (uint32_t)(tsc >> 32);
    
    for (uint32_t i = 0; i < len; i++) {
        if ((i & 63) == 0) {
            /* Generate new block every 64 bytes */
            g_csprng_chacha.counter++;
            g_csprng_chacha.state[12] = g_csprng_chacha.counter;
            chacha20_block(g_csprng_chacha.state);
        }
        
        /* Extract byte from state (little-endian) */
        uint32_t word_idx = ((i & 63) / 4) % 16;
        uint8_t byte_idx = (i & 3);
        out[i] = (g_csprng_chacha.state[word_idx] >> (byte_idx * 8)) & 0xFF;
    }
    
    spin_unlock_irqrestore(&g_csprng_lock, irqf);
}

/* Generate 32-bit random value */
uint32_t csprng_get_u32(void) {
    uint32_t out;
    csprng_get_bytes((uint8_t*)&out, 4);
    return out;
}

/* Generate 64-bit random value */
uint64_t csprng_get_u64(void) {
    uint64_t out;
    csprng_get_bytes((uint8_t*)&out, 8);
    return out;
}