Skip to content

Commit f07fe18

Browse files
Secure C challenge nonce generation
1 parent 09cd06f commit f07fe18

2 files changed

Lines changed: 96 additions & 7 deletions

File tree

rips/rustchain-core/src/anti_spoof/challenge_response.c

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
#include <time.h>
2828
#include <unistd.h>
2929

30+
#ifdef __linux__
31+
#include <errno.h>
32+
#include <sys/random.h>
33+
#endif
34+
3035
#ifdef __APPLE__
3136
#include <sys/sysctl.h>
3237
#include <mach/mach_time.h>
@@ -82,6 +87,33 @@ typedef struct {
8287
char failure_reason[256];
8388
} ValidationResult;
8489

90+
static int fill_secure_nonce(unsigned char *nonce, size_t len) {
91+
#ifdef __APPLE__
92+
arc4random_buf(nonce, len);
93+
return 0;
94+
#elif defined(__linux__)
95+
size_t offset = 0;
96+
97+
while (offset < len) {
98+
ssize_t n = getrandom(nonce + offset, len - offset, 0);
99+
if (n > 0) {
100+
offset += (size_t)n;
101+
continue;
102+
}
103+
if (n < 0 && errno == EINTR) {
104+
continue;
105+
}
106+
return -1;
107+
}
108+
109+
return 0;
110+
#else
111+
(void)nonce;
112+
(void)len;
113+
return -1;
114+
#endif
115+
}
116+
85117
/* PowerPC-specific: Read timebase register */
86118
static inline unsigned long long read_timebase(void) {
87119
#ifdef __ppc__
@@ -290,15 +322,16 @@ static void compute_response_hash(Response *resp, unsigned char *hash) {
290322
/* Generate a challenge */
291323
Challenge generate_challenge(unsigned char type) {
292324
Challenge c;
293-
int i;
325+
326+
memset(&c, 0, sizeof(c));
294327

295328
c.challenge_type = type;
296329
c.timestamp = read_timebase();
297330

298-
/* Generate random nonce */
299-
for (i = 0; i < 32; i++) {
300-
c.nonce[i] = (unsigned char)(rand() ^ (c.timestamp >> (i % 8)));
301-
}
331+
if (fill_secure_nonce(c.nonce, sizeof(c.nonce)) != 0) {
332+
fputs("Failed to generate secure challenge nonce\n", stderr);
333+
exit(EXIT_FAILURE);
334+
}
302335

303336
/* Set expected timing based on challenge type */
304337
switch (type) {
@@ -516,8 +549,6 @@ int main(int argc, char *argv[]) {
516549
printf("║ than to emulate one\" ║\n");
517550
printf("╚══════════════════════════════════════════════════════════════════════╝\n");
518551

519-
srand((unsigned int)time(NULL) ^ (unsigned int)read_timebase());
520-
521552
printf("\n Generating comprehensive challenge...\n");
522553
c = generate_challenge(0); /* Full challenge */
523554

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# SPDX-License-Identifier: MIT
2+
from pathlib import Path
3+
import re
4+
5+
6+
SOURCE_PATH = (
7+
Path(__file__).resolve().parents[1]
8+
/ "rips"
9+
/ "rustchain-core"
10+
/ "src"
11+
/ "anti_spoof"
12+
/ "challenge_response.c"
13+
)
14+
15+
16+
def _source() -> str:
17+
return SOURCE_PATH.read_text(encoding="utf-8")
18+
19+
20+
def _function(source: str, name: str) -> str:
21+
match = re.search(rf"^[^\n]*\b{name}\([^)]*\) \{{.*?^}}", source, re.M | re.S)
22+
assert match is not None
23+
return match.group(0)
24+
25+
26+
def test_challenge_nonce_uses_os_csprng_not_rand() -> None:
27+
source = _source()
28+
29+
assert "rand(" not in source
30+
assert "srand(" not in source
31+
assert "getrandom(" in source
32+
assert "arc4random_buf(" in source
33+
34+
35+
def test_linux_getrandom_result_is_checked_and_retried() -> None:
36+
helper = _function(_source(), "fill_secure_nonce")
37+
38+
assert "while (offset < len)" in helper
39+
assert "getrandom(nonce + offset, len - offset, 0)" in helper
40+
assert "offset += (size_t)n" in helper
41+
assert "errno == EINTR" in helper
42+
assert "(void)ret" not in helper
43+
44+
45+
def test_unsupported_platform_fails_closed_without_weak_fallback() -> None:
46+
helper = _function(_source(), "fill_secure_nonce")
47+
fallback = helper.rsplit("#else", 1)[1]
48+
49+
assert "return -1;" in fallback
50+
assert "read_timebase()" not in fallback
51+
assert ">> (i * 8)" not in fallback
52+
53+
54+
def test_nonce_failure_exits_before_returning_challenge() -> None:
55+
generate_challenge = _function(_source(), "generate_challenge")
56+
57+
assert "fill_secure_nonce(c.nonce, sizeof(c.nonce)) != 0" in generate_challenge
58+
assert "exit(EXIT_FAILURE)" in generate_challenge

0 commit comments

Comments
 (0)