aboutsummaryrefslogtreecommitdiff
path: root/src/audit.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/audit.c')
-rw-r--r--src/audit.c214
1 files changed, 214 insertions, 0 deletions
diff --git a/src/audit.c b/src/audit.c
new file mode 100644
index 0000000..2a3565d
--- /dev/null
+++ b/src/audit.c
@@ -0,0 +1,214 @@
+#include "audit.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "util.h"
+
+static void set_err(char **err, const char *msg)
+{
+ if (err && !*err)
+ *err = xstrdup(msg);
+}
+
+static int64_t audit_last_seq(sqlite3 *db)
+{
+ sqlite3_stmt *st = NULL;
+ int64_t seq = 0;
+ if (sqlite3_prepare_v2(db, "SELECT seq FROM audit_log ORDER BY seq DESC LIMIT 1",
+ -1, &st, NULL) == SQLITE_OK &&
+ sqlite3_step(st) == SQLITE_ROW)
+ seq = sqlite3_column_int64(st, 0);
+ sqlite3_finalize(st);
+ return seq;
+}
+
+static void audit_last_hash(sqlite3 *db, unsigned char out[32])
+{
+ memset(out, 0, 32);
+ sqlite3_stmt *st = NULL;
+ if (sqlite3_prepare_v2(db, "SELECT hash FROM audit_log ORDER BY seq DESC LIMIT 1",
+ -1, &st, NULL) == SQLITE_OK &&
+ sqlite3_step(st) == SQLITE_ROW) {
+ const void *b = sqlite3_column_blob(st, 0);
+ if (b && sqlite3_column_bytes(st, 0) == 32)
+ memcpy(out, b, 32);
+ }
+ sqlite3_finalize(st);
+}
+
+static void append_str16(struct buf *b, const char *s)
+{
+ size_t n = s ? strlen(s) : 0;
+ if (n > 0xffff)
+ n = 0xffff;
+ buf_append_u16be(b, (uint16_t)n);
+ buf_append(b, s ? s : "", n);
+}
+
+static void append_str32(struct buf *b, const char *s)
+{
+ size_t n = s ? strlen(s) : 0;
+ buf_append_u32be(b, (uint32_t)n);
+ buf_append(b, s ? s : "", n);
+}
+
+static void audit_hash(const unsigned char prev[32], int64_t seq, const char *at,
+ int64_t user_id, int64_t token_id, int64_t org_id,
+ const char *action, const char *request_json,
+ const char *result_code, unsigned char out[32])
+{
+ struct buf b;
+ buf_init(&b);
+ buf_append(&b, "bokf-v1-audit\0", 14);
+ buf_append(&b, prev, 32);
+ buf_append_u64be(&b, (uint64_t)seq);
+ append_str16(&b, at);
+ buf_append_u64be(&b, (uint64_t)user_id);
+ buf_append_u64be(&b, (uint64_t)token_id);
+ buf_append_u64be(&b, (uint64_t)org_id);
+ append_str16(&b, action);
+ append_str32(&b, request_json);
+ append_str16(&b, result_code);
+ util_sha256(b.p, b.len, out);
+ buf_free(&b);
+}
+
+int audit_append(sqlite3 *db, int64_t org_id, int64_t user_id, int64_t token_id,
+ const char *action, const char *request_json,
+ const char *result_code, char **err)
+{
+ unsigned char prev[32], hash[32];
+ audit_last_hash(db, prev);
+ int64_t seq = audit_last_seq(db) + 1;
+ char ts[32];
+ util_iso8601(util_now(), ts, sizeof ts);
+ if (!request_json)
+ request_json = "{}";
+ audit_hash(prev, seq, ts, user_id, token_id, org_id, action, request_json,
+ result_code, hash);
+
+ sqlite3_stmt *st = NULL;
+ int rc = sqlite3_prepare_v2(
+ db,
+ "INSERT INTO audit_log(seq,org_id,at,actor_user_id,actor_token_id,"
+ "action,request_json,result_code,hash_prev,hash)"
+ " VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10)",
+ -1, &st, NULL);
+ if (rc != SQLITE_OK) {
+ set_err(err, sqlite3_errmsg(db));
+ return -1;
+ }
+ sqlite3_bind_int64(st, 1, seq);
+ if (org_id)
+ sqlite3_bind_int64(st, 2, org_id);
+ else
+ sqlite3_bind_null(st, 2);
+ sqlite3_bind_text(st, 3, ts, -1, SQLITE_TRANSIENT);
+ if (user_id)
+ sqlite3_bind_int64(st, 4, user_id);
+ else
+ sqlite3_bind_null(st, 4);
+ if (token_id)
+ sqlite3_bind_int64(st, 5, token_id);
+ else
+ sqlite3_bind_null(st, 5);
+ sqlite3_bind_text(st, 6, action, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(st, 7, request_json, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(st, 8, result_code, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_blob(st, 9, prev, 32, SQLITE_TRANSIENT);
+ sqlite3_bind_blob(st, 10, hash, 32, SQLITE_TRANSIENT);
+ rc = sqlite3_step(st);
+ sqlite3_finalize(st);
+ if (rc != SQLITE_DONE) {
+ set_err(err, sqlite3_errmsg(db));
+ return -1;
+ }
+ return 0;
+}
+
+int audit_verify(sqlite3 *db, int64_t *checked, int64_t *bad_seq, char **err)
+{
+ sqlite3_stmt *st = NULL;
+ int rc = sqlite3_prepare_v2(
+ db,
+ "SELECT seq,org_id,at,actor_user_id,actor_token_id,action,request_json,"
+ "result_code,hash_prev,hash FROM audit_log ORDER BY seq",
+ -1, &st, NULL);
+ if (rc != SQLITE_OK) {
+ set_err(err, sqlite3_errmsg(db));
+ return -1;
+ }
+ unsigned char running[32];
+ memset(running, 0, 32);
+ int64_t n = 0;
+ int broken = 0;
+ while (sqlite3_step(st) == SQLITE_ROW) {
+ n++;
+ int64_t seq = sqlite3_column_int64(st, 0);
+ int64_t org_id = sqlite3_column_type(st, 1) == SQLITE_NULL
+ ? 0
+ : sqlite3_column_int64(st, 1);
+ const char *at = (const char *)sqlite3_column_text(st, 2);
+ int64_t uid = sqlite3_column_type(st, 3) == SQLITE_NULL
+ ? 0
+ : sqlite3_column_int64(st, 3);
+ int64_t tid = sqlite3_column_type(st, 4) == SQLITE_NULL
+ ? 0
+ : sqlite3_column_int64(st, 4);
+ const char *action = (const char *)sqlite3_column_text(st, 5);
+ const char *req = (const char *)sqlite3_column_text(st, 6);
+ const char *code = (const char *)sqlite3_column_text(st, 7);
+ const void *hprev = sqlite3_column_blob(st, 8);
+ int hprev_n = sqlite3_column_bytes(st, 8);
+ const void *h = sqlite3_column_blob(st, 9);
+ int h_n = sqlite3_column_bytes(st, 9);
+
+ unsigned char expect[32];
+ audit_hash(running, seq, at ? at : "", uid, tid, org_id,
+ action ? action : "", req ? req : "", code ? code : "",
+ expect);
+ if (hprev_n != 32 || h_n != 32 ||
+ memcmp(hprev, running, 32) != 0 || memcmp(h, expect, 32) != 0) {
+ if (bad_seq)
+ *bad_seq = seq;
+ broken = 1;
+ break;
+ }
+ memcpy(running, expect, 32);
+ }
+ sqlite3_finalize(st);
+ if (checked)
+ *checked = n;
+ return broken ? 1 : 0;
+}
+
+char *audit_args_json(yyjson_val *args)
+{
+ if (!args || yyjson_is_null(args))
+ return xstrdup("{}");
+ if (!yyjson_is_obj(args)) {
+ char *s = yyjson_val_write(args, 0, NULL);
+ return s ? s : xstrdup("{}");
+ }
+ yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
+ yyjson_mut_val *out = yyjson_mut_obj(doc);
+ yyjson_mut_doc_set_root(doc, out);
+ yyjson_obj_iter it = yyjson_obj_iter_with(args);
+ yyjson_val *key;
+ while ((key = yyjson_obj_iter_next(&it))) {
+ const char *k = yyjson_get_str(key);
+ yyjson_val *v = yyjson_obj_iter_get_val(key);
+ if (!k || !v)
+ continue;
+ if (strcmp(k, "password") == 0 || strcmp(k, "token") == 0 ||
+ strcmp(k, "content_base64") == 0 || strcmp(k, "secret") == 0)
+ continue;
+ yyjson_mut_val *copy = yyjson_val_mut_copy(doc, v);
+ if (copy)
+ yyjson_mut_obj_add(out, yyjson_mut_strcpy(doc, k), copy);
+ }
+ char *json = yyjson_mut_write(doc, 0, NULL);
+ yyjson_mut_doc_free(doc);
+ return json ? json : xstrdup("{}");
+}