diff --git a/CMakeLists.txt b/CMakeLists.txt index 411d8c7..d21a663 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES src/se050_tai64n_hw.c src/se050_wireguard.c src/se050_wireguard_se050_rng.c + src/se050_rng_seed.c ) # Create library diff --git a/include/se050_wireguard.h b/include/se050_wireguard.h index 999d945..43a6a17 100644 --- a/include/se050_wireguard.h +++ b/include/se050_wireguard.h @@ -172,6 +172,7 @@ int se050_wireguard_compute_mac2(se050_wireguard_session_t *session, * * Uses system RNG (/dev/urandom on POSIX). * For SE050 hardware RNG, use se050_wireguard_generate_keypair_se050(). + * For CSPRNG (seeded from SE050), use se050_wireguard_generate_keypair_csprng(). * * @param private_key Output: private key (32 bytes) * @param public_key Output: public key (32 bytes) @@ -196,6 +197,44 @@ int se050_wireguard_generate_keypair_se050(se050_session_ctx_t *session, uint8_t *public_key); #endif +/** + * @brief Initialize CSPRNG with seed from SE050 + * + * This should be called once at system startup. After initialization, + * the CSPRNG can generate random numbers without further SE050 access. + * + * @param seed_func Function to get seed from SE050 (called once) + * @param seed_ctx Context for seed function + * @return 0 on success, -1 on error + */ +int se050_csprng_init(int (*seed_func)(uint8_t *out, size_t len, void *ctx), void *seed_ctx); + +/** + * @brief Generate WireGuard keypair using CSPRNG + * + * After calling se050_csprng_init(), use this function to generate keypairs. + * This is ideal for ESP32 and other embedded platforms where I2C access should be minimized. + * + * @param private_key Output: private key (32 bytes) + * @param public_key Output: public key (32 bytes) + * @return 0 on success, -1 on error + */ +int se050_wireguard_generate_keypair_csprng(uint8_t *private_key, uint8_t *public_key); + +/** + * @brief Generate random bytes using CSPRNG + * + * @param out Output buffer + * @param len Number of bytes to generate + * @return 0 on success, -1 on error + */ +int se050_csprng_random(uint8_t *out, size_t len); + +/** + * @brief Cleanup CSPRNG and zeroize sensitive data + */ +void se050_csprng_cleanup(void); + /* ========================================================================= * Constants * ========================================================================= */ diff --git a/src/se050_rng_seed.c b/src/se050_rng_seed.c new file mode 100644 index 0000000..f18087a --- /dev/null +++ b/src/se050_rng_seed.c @@ -0,0 +1,209 @@ +/** + * @file se050_rng_seed.c + * @brief SE050-based CSPRNG with initial seed + * + * This module provides a cryptographically secure pseudo-random number generator + * that is seeded once from SE050 hardware TRNG at startup, then uses ChaCha20 + * to generate the rest of the random stream. + * + * Benefits: + * - Minimal I2C communication (only once at startup) + * - Fast random generation after seeding + * - Cryptographically secure (ChaCha20-based) + * - Suitable for ESP32 and other embedded platforms + * + * License: MIT (Clean-room implementation) + */ + +#include "se050_wireguard.h" +#include "se050_x25519_sw.h" +#include "se050_chacha20_poly1305.h" +#include "se050_crypto_utils.h" +#include +#include +#include + +/* ============================================================================ + * Constants + * ============================================================================ */ + +#define SEED_SIZE 32 +#define COUNTER_SIZE 4 +#define NONCE_SIZE 12 +#define KEY_SIZE 32 + +/* ============================================================================ + * CSPRNG Context + * ============================================================================ */ + +typedef struct { + uint8_t key[KEY_SIZE]; /* ChaCha20 key (from SE050 seed) */ + uint32_t counter; /* Stream counter */ + uint8_t buffer[64]; /* Current block buffer */ + size_t buffer_pos; /* Current position in buffer */ + int initialized; /* Initialization flag */ +} se050_csprng_ctx_t; + +/* Global CSPRNG instance */ +static se050_csprng_ctx_t g_rng; + +/* ============================================================================ + * ChaCha20-based CSPRNG + * ============================================================================ */ + +/** + * @brief Generate a new ChaCha20 block + */ +static void csprng_generate_block(se050_csprng_ctx_t *ctx) +{ + uint8_t nonce[NONCE_SIZE] = {0}; + + /* Set counter in nonce (last 4 bytes) */ + nonce[8] = (ctx->counter >> 0) & 0xff; + nonce[9] = (ctx->counter >> 8) & 0xff; + nonce[10] = (ctx->counter >> 16) & 0xff; + nonce[11] = (ctx->counter >> 24) & 0xff; + ctx->counter++; + + /* Generate ChaCha20 block */ + se050_chacha20_block(ctx->buffer, ctx->key, 0, nonce); + ctx->buffer_pos = 0; +} + +/** + * @brief Generate random bytes using ChaCha20 stream + */ +static int csprng_generate(uint8_t *out, size_t len) +{ + if (!g_rng.initialized) { + return -1; + } + + if (!out || len == 0) { + return -1; + } + + for (size_t i = 0; i < len; i++) { + if (g_rng.buffer_pos >= 64) { + csprng_generate_block(&g_rng); + } + out[i] = g_rng.buffer[g_rng.buffer_pos++]; + } + + return 0; +} + +/* ============================================================================ + * SE050 Seed Integration + * ============================================================================ */ + +/** + * @brief Callback type for SE050 RNG + */ +typedef int (*se050_rng_func_t)(uint8_t *out, size_t len, void *ctx); + +/** + * @brief Initialize CSPRNG with seed from SE050 + * + * @param seed_func Function to get seed from SE050 + * @param seed_ctx Context for seed function + * @return 0 on success, -1 on error + */ +int se050_csprng_init(se050_rng_func_t seed_func, void *seed_ctx) +{ + if (!seed_func) { + return -1; + } + + /* Zeroize context first */ + memset(&g_rng, 0, sizeof(g_rng)); + + /* Get seed from SE050 */ + uint8_t seed[SEED_SIZE]; + if (seed_func(seed, SEED_SIZE, seed_ctx) != 0) { + return -1; + } + + /* Use seed as ChaCha20 key */ + memcpy(g_rng.key, seed, KEY_SIZE); + + /* Clear seed from memory */ + memzero_explicit(seed, SEED_SIZE); + + /* Initialize counter and buffer */ + g_rng.counter = 0; + g_rng.buffer_pos = 64; /* Force initial block generation */ + g_rng.initialized = 1; + + return 0; +} + +/** + * @brief Generate random bytes (public API) + */ +int se050_csprng_generate(uint8_t *out, size_t len) +{ + return csprng_generate(out, len); +} + +/** + * @brief Securely zeroize and cleanup CSPRNG + */ +void se050_csprng_cleanup(void) +{ + if (g_rng.initialized) { + memzero_explicit(g_rng.key, KEY_SIZE); + memzero_explicit(g_rng.buffer, 64); + g_rng.initialized = 0; + } +} + +/* ============================================================================ + * WireGuard Key Generation with CSPRNG + * ============================================================================ */ + +/** + * @brief RNG wrapper for x25519 keypair generation using CSPRNG + */ +static int csprng_wrapper(uint8_t *out, size_t len, void *ctx) +{ + (void)ctx; /* Unused */ + return csprng_generate(out, len); +} + +/** + * @brief Generate WireGuard keypair using seeded CSPRNG + * + * @param private_key Output: private key (32 bytes) + * @param public_key Output: public key (32 bytes) + * @return 0 on success, -1 on error + */ +int se050_wireguard_generate_keypair_csprng(uint8_t *private_key, uint8_t *public_key) +{ + if (!private_key || !public_key) { + return -1; + } + + if (!g_rng.initialized) { + return -1; + } + + se050_x25519_sw_keypair_t keypair; + + if (se050_x25519_sw_generate_keypair(&keypair, csprng_wrapper, NULL) < 0) { + return -1; + } + + memcpy(private_key, keypair.private_key, 32); + memcpy(public_key, keypair.public_key, 32); + + return 0; +} + +/** + * @brief Generate random bytes for general use + */ +int se050_csprng_random(uint8_t *out, size_t len) +{ + return csprng_generate(out, len); +} diff --git a/tests/test_csprng.c b/tests/test_csprng.c new file mode 100644 index 0000000..e8c0f83 --- /dev/null +++ b/tests/test_csprng.c @@ -0,0 +1,156 @@ +/** + * @file test_csprng.c + * @brief CSPRNG Tests + */ + +#include "se050_wireguard.h" +#include "se050_chacha20_poly1305.h" +#include +#include +#include + +static int passed = 0; +static int failed = 0; + +#define TEST_ASSERT(cond, msg) do { \ + if (cond) { \ + printf("[PASS] %s\n", msg); \ + passed++; \ + } else { \ + printf("[FAIL] %s\n", msg); \ + failed++; \ + } \ +} while(0) + +/* Mock SE050 RNG for testing */ +static int mock_se050_rng(uint8_t *out, size_t len, void *ctx) +{ + (void)ctx; + /* Generate deterministic "seed" for testing */ + for (size_t i = 0; i < len; i++) { + out[i] = (uint8_t)(i + 0x42); + } + return 0; +} + +/* Test: CSPRNG initialization */ +static void test_csprng_init(void) +{ + printf("\n--- Test CSPRNG Initialization ---\n"); + + int ret = se050_csprng_init(mock_se050_rng, NULL); + TEST_ASSERT(ret == 0, "CSPRNG init returns 0"); + + se050_csprng_cleanup(); +} + +/* Test: CSPRNG generates non-zero data */ +static void test_csprng_output(void) +{ + printf("\n--- Test CSPRNG Output ---\n"); + + se050_csprng_init(mock_se050_rng, NULL); + + uint8_t rand1[32]; + int ret = se050_csprng_random(rand1, 32); + TEST_ASSERT(ret == 0, "Random generation returns 0"); + + uint8_t all_zero = 1; + for (int i = 0; i < 32; i++) { + if (rand1[i] != 0) all_zero = 0; + } + TEST_ASSERT(all_zero == 0, "Random data is non-zero"); + + se050_csprng_cleanup(); +} + +/* Test: CSPRNG generates different values */ +static void test_csprng_uniqueness(void) +{ + printf("\n--- Test CSPRNG Uniqueness ---\n"); + + se050_csprng_init(mock_se050_rng, NULL); + + uint8_t rand1[32], rand2[32]; + se050_csprng_random(rand1, 32); + se050_csprng_random(rand2, 32); + + TEST_ASSERT(memcmp(rand1, rand2, 32) != 0, "Successive calls produce different values"); + + se050_csprng_cleanup(); +} + +/* Test: CSPRNG keypair generation */ +static void test_csprng_keypair(void) +{ + printf("\n--- Test CSPRNG Keypair ---\n"); + + se050_csprng_init(mock_se050_rng, NULL); + + uint8_t priv[32], pub[32]; + int ret = se050_wireguard_generate_keypair_csprng(priv, pub); + TEST_ASSERT(ret == 0, "Keypair generation returns 0"); + + uint8_t priv_zero = 1, pub_zero = 1; + for (int i = 0; i < 32; i++) { + if (priv[i] != 0) priv_zero = 0; + if (pub[i] != 0) pub_zero = 0; + } + TEST_ASSERT(priv_zero == 0, "Private key is non-zero"); + TEST_ASSERT(pub_zero == 0, "Public key is non-zero"); + + se050_csprng_cleanup(); +} + +/* Test: CSPRNG cleanup zeros memory */ +static void test_csprng_cleanup(void) +{ + printf("\n--- Test CSPRNG Cleanup ---\n"); + + se050_csprng_init(mock_se050_rng, NULL); + se050_csprng_cleanup(); + + /* Try to generate after cleanup - should fail */ + uint8_t rand[32]; + int ret = se050_csprng_random(rand, 32); + TEST_ASSERT(ret != 0, "Generation fails after cleanup"); +} + +/* Test: Multiple keypairs from same seed */ +static void test_multiple_keypairs(void) +{ + printf("\n--- Test Multiple Keypairs ---\n"); + + se050_csprng_init(mock_se050_rng, NULL); + + uint8_t priv1[32], pub1[32]; + uint8_t priv2[32], pub2[32]; + + se050_wireguard_generate_keypair_csprng(priv1, pub1); + se050_wireguard_generate_keypair_csprng(priv2, pub2); + + TEST_ASSERT(memcmp(priv1, priv2, 32) != 0, "Multiple private keys are different"); + TEST_ASSERT(memcmp(pub1, pub2, 32) != 0, "Multiple public keys are different"); + + se050_csprng_cleanup(); +} + +int main(void) +{ + printf("========================================\n"); + printf(" CSPRNG Test Suite\n"); + printf("========================================\n"); + + test_csprng_init(); + test_csprng_output(); + test_csprng_uniqueness(); + test_csprng_keypair(); + test_csprng_cleanup(); + test_multiple_keypairs(); + + printf("\n========================================\n"); + printf(" Results: %d passed, %d failed\n", passed, failed); + printf("========================================\n"); + + return failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +}