Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions rips/rustchain-core/src/anti_spoof/challenge_response.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
#include <time.h>
#include <unistd.h>

#ifdef __linux__
#include <errno.h>
#include <sys/random.h>
#endif

#ifdef __APPLE__
#include <sys/sysctl.h>
#include <mach/mach_time.h>
Expand Down Expand Up @@ -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__
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 */

Expand Down
58 changes: 58 additions & 0 deletions tests/test_challenge_response_secure_nonce.py
Original file line number Diff line number Diff line change
@@ -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