From f07fe1850e26010c400dfa31dec098a375dac9fe Mon Sep 17 00:00:00 2001 From: SimoneMariaRomeo <180769497+SimoneMariaRomeo@users.noreply.github.com> Date: Wed, 13 May 2026 02:02:12 +0700 Subject: [PATCH] Secure C challenge nonce generation --- .../src/anti_spoof/challenge_response.c | 45 +++++++++++--- tests/test_challenge_response_secure_nonce.py | 58 +++++++++++++++++++ 2 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 tests/test_challenge_response_secure_nonce.py diff --git a/rips/rustchain-core/src/anti_spoof/challenge_response.c b/rips/rustchain-core/src/anti_spoof/challenge_response.c index 817c8b2da..7bd4fd72b 100644 --- a/rips/rustchain-core/src/anti_spoof/challenge_response.c +++ b/rips/rustchain-core/src/anti_spoof/challenge_response.c @@ -27,6 +27,11 @@ #include #include +#ifdef __linux__ +#include +#include +#endif + #ifdef __APPLE__ #include #include @@ -82,6 +87,33 @@ typedef struct { char failure_reason[256]; } ValidationResult; +static int fill_secure_nonce(unsigned char *nonce, size_t len) { +#ifdef __APPLE__ + arc4random_buf(nonce, len); + return 0; +#elif defined(__linux__) + size_t offset = 0; + + while (offset < len) { + ssize_t n = getrandom(nonce + offset, len - offset, 0); + if (n > 0) { + offset += (size_t)n; + continue; + } + if (n < 0 && errno == EINTR) { + continue; + } + return -1; + } + + return 0; +#else + (void)nonce; + (void)len; + return -1; +#endif +} + /* PowerPC-specific: Read timebase register */ static inline unsigned long long read_timebase(void) { #ifdef __ppc__ @@ -290,15 +322,16 @@ static void compute_response_hash(Response *resp, unsigned char *hash) { /* Generate a challenge */ Challenge generate_challenge(unsigned char type) { Challenge c; - int i; + + memset(&c, 0, sizeof(c)); c.challenge_type = type; c.timestamp = read_timebase(); - /* Generate random nonce */ - for (i = 0; i < 32; i++) { - c.nonce[i] = (unsigned char)(rand() ^ (c.timestamp >> (i % 8))); - } + if (fill_secure_nonce(c.nonce, sizeof(c.nonce)) != 0) { + fputs("Failed to generate secure challenge nonce\n", stderr); + exit(EXIT_FAILURE); + } /* Set expected timing based on challenge type */ switch (type) { @@ -516,8 +549,6 @@ int main(int argc, char *argv[]) { printf("║ than to emulate one\" ║\n"); printf("╚══════════════════════════════════════════════════════════════════════╝\n"); - srand((unsigned int)time(NULL) ^ (unsigned int)read_timebase()); - printf("\n Generating comprehensive challenge...\n"); c = generate_challenge(0); /* Full challenge */ diff --git a/tests/test_challenge_response_secure_nonce.py b/tests/test_challenge_response_secure_nonce.py new file mode 100644 index 000000000..7d33f8be8 --- /dev/null +++ b/tests/test_challenge_response_secure_nonce.py @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: MIT +from pathlib import Path +import re + + +SOURCE_PATH = ( + Path(__file__).resolve().parents[1] + / "rips" + / "rustchain-core" + / "src" + / "anti_spoof" + / "challenge_response.c" +) + + +def _source() -> str: + return SOURCE_PATH.read_text(encoding="utf-8") + + +def _function(source: str, name: str) -> str: + match = re.search(rf"^[^\n]*\b{name}\([^)]*\) \{{.*?^}}", source, re.M | re.S) + assert match is not None + return match.group(0) + + +def test_challenge_nonce_uses_os_csprng_not_rand() -> None: + source = _source() + + assert "rand(" not in source + assert "srand(" not in source + assert "getrandom(" in source + assert "arc4random_buf(" in source + + +def test_linux_getrandom_result_is_checked_and_retried() -> None: + helper = _function(_source(), "fill_secure_nonce") + + assert "while (offset < len)" in helper + assert "getrandom(nonce + offset, len - offset, 0)" in helper + assert "offset += (size_t)n" in helper + assert "errno == EINTR" in helper + assert "(void)ret" not in helper + + +def test_unsupported_platform_fails_closed_without_weak_fallback() -> None: + helper = _function(_source(), "fill_secure_nonce") + fallback = helper.rsplit("#else", 1)[1] + + assert "return -1;" in fallback + assert "read_timebase()" not in fallback + assert ">> (i * 8)" not in fallback + + +def test_nonce_failure_exits_before_returning_challenge() -> None: + generate_challenge = _function(_source(), "generate_challenge") + + assert "fill_secure_nonce(c.nonce, sizeof(c.nonce)) != 0" in generate_challenge + assert "exit(EXIT_FAILURE)" in generate_challenge