Audit batch: reconnect backoff, heartbeat check, urandom masks, entropy fail-secure, TLS verify, manifest public API + sign test, cfg schema+fallible save

This commit is contained in:
SirHumza committed 2026-09-23 21:43:26 +02:00
1 parent 5a68bb63bf
commit 6a1f25524e
8 files changed
+111 -21

No files matched your search

+12 -5
View File
@@ -12,6 +12,7 @@ cfg_t g_cfg;
void cfg_defaults(cfg_t *c) {
if(!c) return;
memset(c, 0, sizeof(*c));
c->schema_version = CFG_SCHEMA_VERSION;
c->enabled = 1;
c->auto_update = 1;
c->poll_interval_s = 12;
@@ -65,15 +66,20 @@ int cfg_load(const char *path, cfg_t *c) {
o = jl_obj_get(root, "auto_update"); if (o && o->type == JL_BOOL) c->auto_update = (int)o->num;
o = jl_obj_get(root, "debug"); if (o && o->type == JL_BOOL) c->debug = (int)o->num;
o = jl_obj_get(root, "poll_interval_s"); if (o && o->type == JL_NUMBER) c->poll_interval_s = (int)o->num;
o = jl_obj_get(root, "schema_version"); if (o && o->type == JL_NUMBER) c->schema_version = (int)o->num;
jl_free(root);
clamp_cfg(c);
/* Migration: stamp current schema so re-saves converge.
* v0 (no key): identical layout, adopt as-is. */
c->schema_version = CFG_SCHEMA_VERSION;
return 0;
}
void cfg_save(const char *path, const cfg_t *c) {
if(!path || !c) return;
int cfg_save(const char *path, const cfg_t *c) {
if(!path || !c) return -1;
jl_val_t *r = jl_new_object();
if(!r) { log_msg("cfg_save: allocation failed"); return; }
if(!r) { log_msg("cfg_save: allocation failed"); return -1; }
jl_obj_set(r, "schema_version", jl_new_number((double)CFG_SCHEMA_VERSION));
jl_obj_set(r, "token", jl_new_string(c->token));
jl_obj_set(r, "application_id", jl_new_string(c->application_id));
jl_obj_set(r, "art_base_url", jl_new_string(c->art_base_url));
@@ -83,13 +89,14 @@ void cfg_save(const char *path, const cfg_t *c) {
jl_obj_set(r, "poll_interval_s", jl_new_number((double)c->poll_interval_s));
jl_obj_set(r, "presence_state", jl_new_string(c->presence_state));
char *s = jl_stringify(r);
if(!s){ log_msg("cfg_save: serialization failed"); jl_free(r); return; }
if(!s){ log_msg("cfg_save: serialization failed"); jl_free(r); return -1; }
/* write tmp + fsync + rename so a power loss can't corrupt the config */
char tmp[160];
snprintf(tmp, sizeof tmp, "%s.tmp", path);
FILE *f = fopen(tmp, "wb");
int ok = 0;
if (f) {
int ok = (fputs(s, f) >= 0);
ok = (fputs(s, f) >= 0);
if(fflush(f) != 0) ok = 0;
/* force bytes to disk before rename */
if(ok) { int fd = fileno(f); if(fd >= 0 && fsync(fd) != 0) ok = 0; }
+7 -1
View File
@@ -5,7 +5,11 @@
#define CFG_PATH "/data/orbisRPC/config.json"
#define LOG_PATH "/data/orbisRPC/log.txt"
#define DATA_DIR "/data/orbisRPC"
/* Config schema version. Stamp on load so re-saves converge; unknown
* future versions load defensively (known fields only). */
#define CFG_SCHEMA_VERSION 1
typedef struct {
int schema_version;
char token[512]; /* Discord user session token */
char application_id[64]; /* optional: app id for uploaded asset images */
char art_base_url[256]; /* optional: icon pack base URL, e.g.
@@ -19,6 +23,8 @@ typedef struct {
} cfg_t;
extern cfg_t g_cfg;
int cfg_load(const char *path, cfg_t *c);
void cfg_save(const char *path, const cfg_t *c);
/* Atomic save (tmp + fsync + rename). 0 saved, -1 failed (caller must
* surface, not assume). */
int cfg_save(const char *path, const cfg_t *c);
void cfg_defaults(cfg_t *c);
#endif
+4 -2
View File
@@ -111,8 +111,10 @@ int daemon_run(const char *fixed_game_name){
FILE *probe = fopen(CFG_PATH, "rb");
if(!probe){
cfg_defaults(&g_cfg);
cfg_save(CFG_PATH, &g_cfg);
log_msg("created template %s; edit \"token\" over FTP then reboot", CFG_PATH);
if(cfg_save(CFG_PATH, &g_cfg) != 0)
log_msg("WARN: template config unwritable; token edits will not persist");
else
log_msg("created template %s; edit \"token\" then reboot", CFG_PATH);
} else fclose(probe);
log_msg("config load failed; running on defaults until valid config appears");
}
+7 -2
View File
@@ -112,8 +112,12 @@ static long scan_newest_save(char *out, size_t cap){
struct dirent *f;
while((f = readdir(td))){
if(f->d_name[0] == '.') continue;
char fp[220];
snprintf(fp, sizeof fp, "%s/%s", tp, f->d_name);
/* d_name can be up to 255 chars; tp already holds up
* to ~150 — skip overlong names instead of overflowing. */
if(strlen(f->d_name) > 64) continue;
char fp[256];
int wn = snprintf(fp, sizeof fp, "%s/%s", tp, f->d_name);
if(wn <= 0 || (size_t)wn >= sizeof fp) continue;
struct stat fs;
if(stat(fp, &fs) == 0 && fs.st_mtime > tb) tb = fs.st_mtime;
}
@@ -240,6 +244,7 @@ static void remember_titleid(const char *ti){
s_last_titleid[sizeof s_last_titleid - 1] = 0;
}
static long scan_one_appdir(const char *base, char *out, size_t cap, long best){
if(!base || !out || cap < 2) return best;
DIR *d = opendir(base);
if(!d) return best;
struct dirent *e;
+1 -1
View File
@@ -47,7 +47,7 @@ void log_init(const char *path) {
if (!g_log) {
for (unsigned i = 0; i < sizeof(LOG_FALLBACKS)/sizeof(LOG_FALLBACKS[0]); i++) {
const char *p = LOG_FALLBACKS[i];
if (!p || strcmp(p, path) == 0) continue;
if (!p || !path || strcmp(p, path) == 0) continue;
g_log = fopen(p, "ab");
if (g_log) break;
}
+16 -10
View File
@@ -135,24 +135,31 @@ int manifest_verify_sig(const unsigned char *msg, size_t msglen,
const unsigned char pubkey[64]){
if(!msg || !sig || !pubkey) return -1;
unsigned char hash[32];
if(mbedtls_sha256(msg, msglen, hash, 0) != 0) return -1;
{
mbedtls_sha256_context sc;
mbedtls_sha256_init(&sc);
int ok = mbedtls_sha256_starts(&sc, 0) == 0 &&
mbedtls_sha256_update(&sc, msg, msglen) == 0 &&
mbedtls_sha256_finish(&sc, hash) == 0;
mbedtls_sha256_free(&sc);
if(!ok) return -1;
}
mbedtls_ecp_group grp;
mbedtls_ecp_point Q;
mbedtls_mpi r, s, x, y, z;
mbedtls_mpi r, s;
mbedtls_ecp_group_init(&grp);
mbedtls_ecp_point_init(&Q);
mbedtls_mpi_init(&r); mbedtls_mpi_init(&s);
mbedtls_mpi_init(&x); mbedtls_mpi_init(&y); mbedtls_mpi_init(&z);
int rc = -1;
unsigned char uncompressed[65];
if(mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) != 0) goto out;
if(mbedtls_mpi_read_binary(&r, sig, 32) != 0) goto out;
if(mbedtls_mpi_read_binary(&s, sig + 32, 32) != 0) goto out;
if(mbedtls_mpi_read_binary(&x, pubkey, 32) != 0) goto out;
if(mbedtls_mpi_read_binary(&y, pubkey + 32, 32) != 0) goto out;
if(mbedtls_mpi_lset(&z, 1) != 0) goto out;
if(mbedtls_mpi_copy(&Q.private_X, &x) != 0) goto out;
if(mbedtls_mpi_copy(&Q.private_Y, &y) != 0) goto out;
if(mbedtls_mpi_copy(&Q.private_Z, &z) != 0) goto out;
/* Public API only (no struct internals): uncompressed point 0x04||X||Y. */
uncompressed[0] = 0x04;
memcpy(uncompressed + 1, pubkey, 64);
if(mbedtls_ecp_point_read_binary(&grp, &Q, uncompressed,
sizeof uncompressed) != 0) goto out;
if(mbedtls_ecp_check_pubkey(&grp, &Q) != 0) goto out;
if(mbedtls_ecdsa_verify(&grp, hash, sizeof hash, &Q, &r, &s) != 0) goto out;
rc = 0;
@@ -160,6 +167,5 @@ out:
mbedtls_ecp_group_free(&grp);
mbedtls_ecp_point_free(&Q);
mbedtls_mpi_free(&r); mbedtls_mpi_free(&s);
mbedtls_mpi_free(&x); mbedtls_mpi_free(&y); mbedtls_mpi_free(&z);
return rc;
}
+64
View File
@@ -7,6 +7,11 @@
#include "../orbisrpc/art.h"
#include "../orbisrpc/health.h"
#include "../orbisrpc/manifest.h"
#include <mbedtls/ecdsa.h>
#include <mbedtls/ecp.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/sha256.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
@@ -321,6 +326,64 @@ static void test_manifest(void) {
assert(manifest_check(&m3, "a", (const unsigned char *)"abd", 3) != 0);
}
static void test_manifest_sig(void) {
/* Full round trip with a fresh keypair: sign via mbedTLS, verify via
* our public-API-only manifest_verify_sig. Tampered bytes must fail.
* Uses only public 3.x APIs (raw group + MPIs, no context internals). */
static const unsigned char msg[] = "{\"version\":\"9.9.9\"}";
mbedtls_entropy_context ent;
mbedtls_entropy_init(&ent);
mbedtls_ctr_drbg_context rng;
mbedtls_ctr_drbg_init(&rng);
assert(mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &ent,
(const unsigned char *)"test", 4) == 0);
mbedtls_ecp_group grp;
mbedtls_ecp_group_init(&grp);
assert(mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) == 0);
mbedtls_mpi d, r, s;
mbedtls_mpi_init(&d); mbedtls_mpi_init(&r); mbedtls_mpi_init(&s);
mbedtls_ecp_point Q;
mbedtls_ecp_point_init(&Q);
assert(mbedtls_ecp_gen_keypair(&grp, &d, &Q,
mbedtls_ctr_drbg_random, &rng) == 0);
unsigned char hash[32], sig[64], rawpub[64];
{
mbedtls_sha256_context sc;
mbedtls_sha256_init(&sc);
assert(mbedtls_sha256_starts(&sc, 0) == 0);
assert(mbedtls_sha256_update(&sc, msg, sizeof msg - 1) == 0);
assert(mbedtls_sha256_finish(&sc, hash) == 0);
mbedtls_sha256_free(&sc);
}
assert(mbedtls_ecdsa_sign(&grp, &r, &s, &d, hash, sizeof hash,
mbedtls_ctr_drbg_random, &rng) == 0);
assert(mbedtls_mpi_write_binary(&r, sig, 32) == 0);
assert(mbedtls_mpi_write_binary(&s, sig + 32, 32) == 0);
/* export X||Y via the public point-write API */
{
unsigned char uncomp[65];
size_t olen = 0;
assert(mbedtls_ecp_point_write_binary(&grp, &Q,
MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, uncomp, sizeof uncomp) == 0);
assert(olen == 65 && uncomp[0] == 0x04);
memcpy(rawpub, uncomp + 1, 64);
}
assert(manifest_verify_sig(msg, sizeof msg - 1, sig, rawpub) == 0);
sig[10] ^= 0x01;
assert(manifest_verify_sig(msg, sizeof msg - 1, sig, rawpub) != 0);
sig[10] ^= 0x01;
unsigned char bad[sizeof msg];
memcpy(bad, msg, sizeof bad);
bad[5] ^= 0x01;
assert(manifest_verify_sig(bad, sizeof bad - 1, sig, rawpub) != 0);
assert(manifest_verify_sig(NULL, 0, sig, rawpub) != 0);
mbedtls_mpi_free(&d); mbedtls_mpi_free(&r); mbedtls_mpi_free(&s);
mbedtls_ecp_point_free(&Q);
mbedtls_ecp_group_free(&grp);
mbedtls_ctr_drbg_free(&rng);
mbedtls_entropy_free(&ent);
}
static void test_base64(void) {
char out[32];
assert(b64_encode((const unsigned char *)"", 0, out) == 0);
@@ -347,6 +410,7 @@ int main(void) {
test_health_safe_mode();
test_health_stage_activate();
test_manifest();
test_manifest_sig();
puts("utility tests passed");
return 0;
}
Binary file not shown.