18#if DETWS_ENABLE_AUDIT_LOG
31 DetwsAuditEntry ring[DETWS_AUDIT_LOG_ENTRIES];
35 uint8_t anchor[DETWS_AUDIT_HASH_LEN] = {};
36 detws_audit_sink_fn sink =
nullptr;
40inline uint16_t idx(
const AuditCtx &c, uint16_t i)
42 return (uint16_t)((c.head + i) % DETWS_AUDIT_LOG_ENTRIES);
45void put_le32(uint8_t out[4], uint32_t v)
47 out[0] = (uint8_t)(v & 0xFF);
48 out[1] = (uint8_t)((v >> 8) & 0xFF);
49 out[2] = (uint8_t)((v >> 16) & 0xFF);
50 out[3] = (uint8_t)((v >> 24) & 0xFF);
55void chain_hash(
const uint8_t prev[DETWS_AUDIT_HASH_LEN],
const DetwsAuditEntry *e, uint8_t out[DETWS_AUDIT_HASH_LEN])
66 uint8_t mlen = (uint8_t)strnlen(e->msg, DETWS_AUDIT_MSG_LEN - 1);
74size_t json_escape(
char *out,
size_t pos,
size_t cap,
const char *s)
76 for (
size_t i = 0; s[i]; i++)
78 unsigned char ch = (
unsigned char)s[i];
79 const char *esc =
nullptr;
94 ub[0] =
'\\', ub[1] =
'u', ub[2] =
'0', ub[3] =
'0';
104 out[pos++] = (char)ch;
109 memcpy(out + pos, esc, n);
115size_t hex_hash(
char *out,
size_t pos,
size_t cap,
const uint8_t *h)
117 if (pos + DETWS_AUDIT_HASH_LEN * 2 > cap)
119 for (
size_t i = 0; i < DETWS_AUDIT_HASH_LEN; i++)
128void detws_audit_reset(
void)
133 memset(s_audit.anchor, 0,
sizeof(s_audit.anchor));
136void detws_audit_set_sink(detws_audit_sink_fn sink)
141uint32_t detws_audit_append(DetwsAuditCat category,
const char *msg)
144 uint8_t prev[DETWS_AUDIT_HASH_LEN];
145 if (s_audit.count == 0)
146 memcpy(prev, s_audit.anchor, DETWS_AUDIT_HASH_LEN);
148 memcpy(prev, s_audit.ring[idx(s_audit, (uint16_t)(s_audit.count - 1))].hash, DETWS_AUDIT_HASH_LEN);
153 if (s_audit.count == DETWS_AUDIT_LOG_ENTRIES)
155 memcpy(s_audit.anchor, s_audit.ring[s_audit.head].hash, DETWS_AUDIT_HASH_LEN);
156 s_audit.head = (uint16_t)((s_audit.head + 1) % DETWS_AUDIT_LOG_ENTRIES);
160 DetwsAuditEntry *e = &s_audit.ring[idx(s_audit, s_audit.count)];
161 e->seq = ++s_audit.seq;
163 e->category = category;
166 size_t n = strnlen(msg, DETWS_AUDIT_MSG_LEN - 1);
167 memcpy(e->msg, msg, n);
172 chain_hash(prev, e, e->hash);
180uint16_t detws_audit_count(
void)
182 return s_audit.count;
185const DetwsAuditEntry *detws_audit_at(uint16_t i)
187 if (i >= s_audit.count)
189 return &s_audit.ring[idx(s_audit, i)];
192bool detws_audit_verify(uint32_t *first_broken_seq)
194 uint8_t expected[DETWS_AUDIT_HASH_LEN];
195 memcpy(expected, s_audit.anchor, DETWS_AUDIT_HASH_LEN);
196 for (uint16_t i = 0; i < s_audit.count; i++)
198 const DetwsAuditEntry *e = &s_audit.ring[idx(s_audit, i)];
199 uint8_t h[DETWS_AUDIT_HASH_LEN];
200 chain_hash(expected, e, h);
201 if (memcmp(h, e->hash, DETWS_AUDIT_HASH_LEN) != 0)
203 if (first_broken_seq)
204 *first_broken_seq = e->seq;
207 memcpy(expected, e->hash, DETWS_AUDIT_HASH_LEN);
212const char *detws_audit_cat_name(DetwsAuditCat category)
216 case DetwsAuditCat::DETWS_AUDIT_AUTH:
218 case DetwsAuditCat::DETWS_AUDIT_AUTH_FAIL:
220 case DetwsAuditCat::DETWS_AUDIT_ACCESS:
222 case DetwsAuditCat::DETWS_AUDIT_CONFIG:
224 case DetwsAuditCat::DETWS_AUDIT_ADMIN:
231int detws_audit_format(
const DetwsAuditEntry *e,
char *out,
size_t cap)
233 if (!e || !out || cap == 0)
235 int head = snprintf(out, cap,
"{\"seq\":%lu,\"ts\":%lu,\"cat\":\"%s\",\"msg\":\"", (
unsigned long)e->seq,
236 (
unsigned long)e->ts, detws_audit_cat_name(e->category));
237 if (head < 0 || (
size_t)head >= cap)
239 size_t pos = (size_t)head;
240 pos = json_escape(out, pos, cap, e->msg);
243 static const char mid[] =
"\",\"hash\":\"";
244 size_t mid_len =
sizeof(mid) - 1;
245 if (pos + mid_len > cap)
247 memcpy(out + pos, mid, mid_len);
249 pos = hex_hash(out, pos, cap, e->hash);
262int detws_audit_dump_json(
char *out,
size_t cap)
264 if (!out || cap == 0)
267 bool intact = detws_audit_verify(&broken);
271 head = snprintf(out, cap,
"{\"intact\":true,\"count\":%u,\"entries\":[", (
unsigned)s_audit.count);
273 head = snprintf(out, cap,
"{\"intact\":false,\"first_broken\":%lu,\"count\":%u,\"entries\":[",
274 (
unsigned long)broken, (
unsigned)s_audit.count);
275 if (head < 0 || (
size_t)head >= cap)
277 size_t pos = (size_t)head;
279 for (uint16_t i = 0; i < s_audit.count; i++)
287 int n = detws_audit_format(&s_audit.ring[idx(s_audit, i)], out + pos, cap - pos);
Tamper-evident, hash-chained audit log (DETWS_ENABLE_AUDIT_LOG).
Pluggable monotonic clock for all library timing.
uint32_t detws_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Tiny no-stdlib hex encode / decode / nibble helpers (one shared copy).
char det_hex_digit(int nibble, bool upper=false)
Nibble 0..15 -> hex char (lowercase, or uppercase when upper).
void ssh_sha256_init(SshSha256Ctx *ctx)
Initialize a streaming SHA-256 context.
void ssh_sha256_update(SshSha256Ctx *ctx, const uint8_t *data, size_t len)
Feed len bytes of data into the running hash.
void ssh_sha256_final(SshSha256Ctx *ctx, uint8_t digest[SSH_SHA256_DIGEST_LEN])
Finalize the hash and write the 32-byte digest.
SHA-256 (FIPS 180-4) - streaming context and one-shot API.
Streaming SHA-256 context.