19#if PC_ENABLE_AUDIT_LOG
32 pc_audit_entry ring[PC_AUDIT_LOG_ENTRIES];
36 uint8_t anchor[PC_AUDIT_HASH_LEN] = {};
37 pc_audit_sink_fn sink =
nullptr;
41inline uint16_t idx(
const AuditCtx &c, uint16_t i)
43 return (uint16_t)((c.head + i) % PC_AUDIT_LOG_ENTRIES);
46void put_le32(uint8_t out[4], uint32_t v)
48 out[0] = (uint8_t)(v & 0xFF);
49 out[1] = (uint8_t)((v >> 8) & 0xFF);
50 out[2] = (uint8_t)((v >> 16) & 0xFF);
51 out[3] = (uint8_t)((v >> 24) & 0xFF);
56void chain_hash(
const uint8_t prev[PC_AUDIT_HASH_LEN],
const pc_audit_entry *e, uint8_t out[PC_AUDIT_HASH_LEN])
67 uint8_t mlen = (uint8_t)strnlen(e->msg, PC_AUDIT_MSG_LEN - 1);
75size_t json_escape(
char *out,
size_t pos,
size_t cap,
const char *s)
77 for (
size_t i = 0; s[i]; i++)
79 unsigned char ch = (
unsigned char)s[i];
80 const char *esc =
nullptr;
105 ub[0] =
'\\', ub[1] =
'u', ub[2] =
'0', ub[3] =
'0';
117 out[pos++] = (char)ch;
124 memcpy(out + pos, esc, n);
130size_t hex_hash(
char *out,
size_t pos,
size_t cap,
const uint8_t *h)
132 if (pos + PC_AUDIT_HASH_LEN * 2 > cap)
136 for (
size_t i = 0; i < PC_AUDIT_HASH_LEN; i++)
145void pc_audit_reset(
void)
150 memset(s_audit.anchor, 0,
sizeof(s_audit.anchor));
153void pc_audit_set_sink(pc_audit_sink_fn sink)
158uint32_t pc_audit_append(pc_audit_cat category,
const char *msg)
161 uint8_t prev[PC_AUDIT_HASH_LEN];
162 if (s_audit.count == 0)
164 memcpy(prev, s_audit.anchor, PC_AUDIT_HASH_LEN);
168 memcpy(prev, s_audit.ring[idx(s_audit, (uint16_t)(s_audit.count - 1))].hash, PC_AUDIT_HASH_LEN);
174 if (s_audit.count == PC_AUDIT_LOG_ENTRIES)
176 memcpy(s_audit.anchor, s_audit.ring[s_audit.head].hash, PC_AUDIT_HASH_LEN);
177 s_audit.head = (uint16_t)((s_audit.head + 1) % PC_AUDIT_LOG_ENTRIES);
181 pc_audit_entry *e = &s_audit.ring[idx(s_audit, s_audit.count)];
182 e->seq = ++s_audit.seq;
184 e->category = category;
187 size_t n = strnlen(msg, PC_AUDIT_MSG_LEN - 1);
188 memcpy(e->msg, msg, n);
195 chain_hash(prev, e, e->hash);
205uint16_t pc_audit_count(
void)
207 return s_audit.count;
210const pc_audit_entry *pc_audit_at(uint16_t i)
212 if (i >= s_audit.count)
216 return &s_audit.ring[idx(s_audit, i)];
219bool pc_audit_verify(uint32_t *first_broken_seq)
221 uint8_t expected[PC_AUDIT_HASH_LEN];
222 memcpy(expected, s_audit.anchor, PC_AUDIT_HASH_LEN);
223 for (uint16_t i = 0; i < s_audit.count; i++)
225 const pc_audit_entry *e = &s_audit.ring[idx(s_audit, i)];
226 uint8_t h[PC_AUDIT_HASH_LEN];
227 chain_hash(expected, e, h);
228 if (memcmp(h, e->hash, PC_AUDIT_HASH_LEN) != 0)
230 if (first_broken_seq)
232 *first_broken_seq = e->seq;
236 memcpy(expected, e->hash, PC_AUDIT_HASH_LEN);
241const char *pc_audit_cat_name(pc_audit_cat category)
245 case pc_audit_cat::PC_AUDIT_AUTH:
247 case pc_audit_cat::PC_AUDIT_AUTH_FAIL:
249 case pc_audit_cat::PC_AUDIT_ACCESS:
251 case pc_audit_cat::PC_AUDIT_CONFIG:
253 case pc_audit_cat::PC_AUDIT_ADMIN:
260int pc_audit_format(
const pc_audit_entry *e,
char *out,
size_t cap)
262 if (!e || !out || cap == 0)
266 pc_sb sb_out = {out, cap, 0,
true};
268 pc_sb_u32(&sb_out, (uint32_t)((
unsigned long)e->seq));
270 pc_sb_u32(&sb_out, (uint32_t)((
unsigned long)e->ts));
272 pc_sb_put(&sb_out, pc_audit_cat_name(e->category));
282 if (head < 0 || (
size_t)head >= cap)
286 size_t pos = (size_t)head;
287 pos = json_escape(out, pos, cap, e->msg);
292 static const char mid[] =
"\",\"hash\":\"";
293 size_t mid_len =
sizeof(mid) - 1;
294 if (pos + mid_len > cap)
298 memcpy(out + pos, mid, mid_len);
300 pos = hex_hash(out, pos, cap, e->hash);
319int pc_audit_dump_json(
char *out,
size_t cap)
321 if (!out || cap == 0)
326 bool intact = pc_audit_verify(&broken);
331 pc_sb sb_out2 = {out, cap, 0,
true};
332 pc_sb_put(&sb_out2,
"{\"intact\":true,\"count\":");
333 pc_sb_u32(&sb_out2, (uint32_t)((
unsigned)s_audit.count));
339 pc_sb sb_out3 = {out, cap, 0,
true};
340 pc_sb_put(&sb_out3,
"{\"intact\":false,\"first_broken\":");
341 pc_sb_u32(&sb_out3, (uint32_t)((
unsigned long)broken));
343 pc_sb_u32(&sb_out3, (uint32_t)((
unsigned)s_audit.count));
354 if (head < 0 || (
size_t)head >= cap)
358 size_t pos = (size_t)head;
360 for (uint16_t i = 0; i < s_audit.count; i++)
375 int n = pc_audit_format(&s_audit.ring[idx(s_audit, i)], out + pos, cap - pos);
Tamper-evident, hash-chained audit log (PC_ENABLE_AUDIT_LOG).
Pluggable monotonic clock for all library timing.
uint32_t pc_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Base-16 conversion between raw bytes and their ASCII digits.
char pc_hex_digit(uint8_t nibble, bool upper=false)
Low nibble of nibble as a hex character, uppercase when upper.
PC_CRYPTO_HOT void pc_sha256_init(pc_sha256_ctx *ctx)
Initialize a streaming SHA-256 context (ctx must not be NULL).
void pc_sha256_final(pc_sha256_ctx *ctx, uint8_t digest[PC_SHA256_DIGEST_LEN])
Finalize the hash and write the 32-byte digest. The context is undefined afterwards; call init() agai...
void pc_sha256_update(pc_sha256_ctx *ctx, const uint8_t *data, size_t len)
Feed len bytes of data into the running hash.
SHA-256 (FIPS 180-4) - streaming context and one-shot API.
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Bump-append target; ok latches false once an append would overflow cap.
Streaming SHA-256 context.