diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-20 15:41:55 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-20 15:42:40 +0200 |
| commit | f3b3f2c4f944c98a341f406abd2e4a6b7b69da36 (patch) | |
| tree | f7b2e25937c4c10466e2cce7c2c7a9a6eeb6d7c8 /tests | |
| parent | 21057cd2f2ab298300a246b9fba0d38d740c9697 (diff) | |
| download | bokf-f3b3f2c4f944c98a341f406abd2e4a6b7b69da36.tar.gz bokf-f3b3f2c4f944c98a341f406abd2e4a6b7b69da36.zip | |
settings: encrypted secrets and SMTP settings
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_core.c | 328 |
1 files changed, 317 insertions, 11 deletions
diff --git a/tests/test_core.c b/tests/test_core.c index fa7b5e4..47b6acf 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -18,6 +18,7 @@ #include "db.h" #include "invoice.h" #include "protocol.h" +#include "secret.h" #include "sessions.h" #include "util.h" #include "yyjson.h" @@ -282,7 +283,8 @@ static void echo_child(int wfd, int tls) if (memchr(buf, '\n', got)) break; } - (void)write(cfd, "tcp-ok", 6); + ssize_t wr = write(cfd, "tcp-ok", 6); + (void)wr; } close(cfd); close(sfd); @@ -567,6 +569,106 @@ static void test_pre_migration_snapshot(const char *tmpdir, unlink(blocker); } +static void test_secret(void) +{ + static const char hexkey[] = + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; + static const char otherkey[] = + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + unsigned char raw[32]; + for (int i = 0; i < 32; i++) + raw[i] = (unsigned char)(i * 7 + 1); + char *b64key = util_b64(raw, sizeof raw); + + CHECK(secret_key_is_secret("smtp_password")); + CHECK(!secret_key_is_secret("smtp_host")); + CHECK(!secret_key_is_secret(NULL)); + + unsetenv("BOKFD_SECRET_KEY"); + CHECK(!secret_available()); + char *out = NULL; + CHECK(secret_encrypt("hemlig", &out) == -1); + CHECK(out == NULL); + CHECK(secret_decrypt("enc:v1:AAAA:BBBB", &out) == -1); + CHECK(out == NULL); + CHECK(secret_decrypt("plaintext", &out) == -1); + + setenv("BOKFD_SECRET_KEY", hexkey, 1); + CHECK(secret_available()); + out = NULL; + CHECK(secret_encrypt("hemligt lösenord", &out) == 0); + CHECK(out && strncmp(out, "enc:v1:", 7) == 0); + CHECK(out && strstr(out, "hemligt") == NULL); + char *dec = NULL; + CHECK(secret_decrypt(out, &dec) == 0); + CHECK(dec && strcmp(dec, "hemligt lösenord") == 0); + free(dec); + + char *tampered = xstrdup(out); + char *sep = strchr(tampered + 7, ':'); + CHECK(sep != NULL); + if (sep) { + char *ctpart = sep + 1; + ctpart[0] = ctpart[0] == 'A' ? 'B' : 'A'; + } + dec = NULL; + CHECK(secret_decrypt(tampered, &dec) == -1); + CHECK(dec == NULL); + free(tampered); + + tampered = xstrdup(out); + tampered[7] = tampered[7] == 'A' ? 'B' : 'A'; + dec = NULL; + CHECK(secret_decrypt(tampered, &dec) == -1); + free(tampered); + + setenv("BOKFD_SECRET_KEY", otherkey, 1); + dec = NULL; + CHECK(secret_decrypt(out, &dec) == -1); + CHECK(dec == NULL); + free(out); + + setenv("BOKFD_SECRET_KEY", b64key, 1); + CHECK(secret_available()); + out = NULL; + CHECK(secret_encrypt("kort", &out) == 0); + dec = NULL; + CHECK(secret_decrypt(out, &dec) == 0); + CHECK(dec && strcmp(dec, "kort") == 0); + free(dec); + free(out); + + char *nopad = xstrdup(b64key); + size_t n = strlen(nopad); + while (n > 0 && nopad[n - 1] == '=') + nopad[--n] = '\0'; + setenv("BOKFD_SECRET_KEY", nopad, 1); + CHECK(secret_available()); + out = NULL; + CHECK(secret_encrypt("utan padding", &out) == 0); + dec = NULL; + CHECK(secret_decrypt(out, &dec) == 0); + CHECK(dec && strcmp(dec, "utan padding") == 0); + free(dec); + free(out); + free(nopad); + + setenv("BOKFD_SECRET_KEY", "abcd", 1); + CHECK(!secret_available()); + out = NULL; + CHECK(secret_encrypt("x", &out) == -1); + CHECK(secret_decrypt("enc:v1:AAAA:BBBB", &out) == -1); + + setenv("BOKFD_SECRET_KEY", "000102030405060708090a0b0c0d0e0f", 1); + CHECK(!secret_available()); + setenv("BOKFD_SECRET_KEY", "zzzz", 1); + CHECK(!secret_available()); + + unsetenv("BOKFD_SECRET_KEY"); + CHECK(!secret_available()); + free(b64key); +} + int main(void) { char tmpdir[] = "/tmp/bokf-test-XXXXXX"; @@ -603,6 +705,8 @@ int main(void) } sessions_init(3600); + test_secret(); + yyjson_doc *d; d = call("{\"v\":1,\"id\":\"1\",\"cmd\":\"health\"}"); @@ -1322,7 +1426,7 @@ int main(void) CHECK_OK(d); CHECK_STR(d, "result.org_nr", "559331-2126"); CHECK_STR(d, "result.period", "202601"); - char *b64 = jstr(d, "result.content_base64"); + const char *b64 = jstr(d, "result.content_base64"); unsigned char *xmlb = NULL; size_t xmll = 0; CHECK(b64 && util_b64_decode(b64, strlen(b64), &xmlb, &xmll) == 0); @@ -1844,14 +1948,14 @@ int main(void) "#TRANS 2010 {} -1000.00\r\n" "#TRANS 2010 {} 0.00\r\n" "}\r\n"; - char *b64 = util_b64((const unsigned char *)sie_crlf, - strlen(sie_crlf)); - d = call_sie_import(g_session, org2, b64, 0); + char *sie_b64a = util_b64((const unsigned char *)sie_crlf, + strlen(sie_crlf)); + d = call_sie_import(g_session, org2, sie_b64a, 0); CHECK_OK(d); CHECK(jint(d, "result.vouchers") == 1); int64_t fy_a = jint(d, "result.fiscal_year_id"); yyjson_doc_free(d); - free(b64); + free(sie_b64a); d = call(reqf("{\"v\":1,\"id\":\"77a\",\"cmd\":\"fiscal_year.get\"," "\"session\":\"%s\",\"org\":%d,\"args\":{\"id\":%lld}}", @@ -1877,13 +1981,13 @@ int main(void) "#TRANS 1930 {} 50.00\n" "#TRANS 2010 {} -50.00\n" "}\n"; - char *b64 = util_b64((const unsigned char *)sie_year2, - strlen(sie_year2)); - d = call_sie_import(g_session, org2, b64, 0); + char *sie_b64b = util_b64((const unsigned char *)sie_year2, + strlen(sie_year2)); + d = call_sie_import(g_session, org2, sie_b64b, 0); CHECK_OK(d); int64_t fy_b = jint(d, "result.fiscal_year_id"); yyjson_doc_free(d); - free(b64); + free(sie_b64b); d = call(reqf("{\"v\":1,\"id\":\"77b\",\"cmd\":\"report.trial_balance\"," "\"session\":\"%s\",\"org\":%d,\"args\":" @@ -1972,6 +2076,208 @@ int main(void) CHECK_STR(d, "result.attachment_dir", "/tmp/bilagor"); yyjson_doc_free(d); + /* ---------------- settings: SMTP and encrypted password ----------- */ + + d = call(reqf("{\"v\":1,\"id\":\"102\",\"cmd\":\"settings.get\"," + "\"session\":\"%s\",\"org\":%d}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.smtp_security", "starttls"); + CHECK(jget(d, "result.smtp_password") == NULL); + CHECK(!jbool(d, "result.smtp_password_set")); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"103\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_host\",\"value\":\"smtp.example.se\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.value", "smtp.example.se"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"104\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_host\",\"value\":\"bad\\u0001host\"}}", + g_session, (int)org_id)); + CHECK_STR(d, "error.code", "INVALID_ARGS"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"105\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_port\",\"value\":\"587\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.value", "587"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"106\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_port\",\"value\":\"0\"}}", + g_session, (int)org_id)); + CHECK_STR(d, "error.code", "INVALID_ARGS"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"107\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_port\",\"value\":\"70000\"}}", + g_session, (int)org_id)); + CHECK_STR(d, "error.code", "INVALID_ARGS"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"108\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_security\",\"value\":\"ssl\"}}", + g_session, (int)org_id)); + CHECK_STR(d, "error.code", "INVALID_ARGS"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"109\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_user\",\"value\":\"faktura@example.se\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"110\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_from\",\"value\":\"Bokf AB <a@b.se>\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"111\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_reply_to\",\"value\":\"svar@example.se\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"112\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_security\",\"value\":\"tls\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.value", "tls"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"113\",\"cmd\":\"settings.get\"," + "\"session\":\"%s\",\"org\":%d}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.smtp_host", "smtp.example.se"); + CHECK_STR(d, "result.smtp_port", "587"); + CHECK_STR(d, "result.smtp_user", "faktura@example.se"); + CHECK_STR(d, "result.smtp_from", "Bokf AB <a@b.se>"); + CHECK_STR(d, "result.smtp_reply_to", "svar@example.se"); + CHECK_STR(d, "result.smtp_security", "tls"); + yyjson_doc_free(d); + + unsetenv("BOKFD_SECRET_KEY"); + d = call(reqf("{\"v\":1,\"id\":\"114\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_password\",\"value\":\"hunter2\"}}", + g_session, (int)org_id)); + CHECK_STR(d, "error.code", "INTERNAL"); + CHECK(jstr(d, "error.message") && + strstr(jstr(d, "error.message"), "BOKFD_SECRET_KEY") != NULL); + yyjson_doc_free(d); + + char *secret_stored = db_text(g_db, reqf("SELECT value FROM settings WHERE" + " org_id=%d AND key='smtp_password'", + (int)org_id)); + CHECK(secret_stored == NULL); + free(secret_stored); + + setenv("BOKFD_SECRET_KEY", "abcd", 1); + d = call(reqf("{\"v\":1,\"id\":\"115\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_password\",\"value\":\"hunter2\"}}", + g_session, (int)org_id)); + CHECK_STR(d, "error.code", "INTERNAL"); + yyjson_doc_free(d); + unsetenv("BOKFD_SECRET_KEY"); + + setenv("BOKFD_SECRET_KEY", + "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f", + 1); + d = call(reqf("{\"v\":1,\"id\":\"116\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_password\",\"value\":\"hunter2\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.value", "[redacted]"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"117\",\"cmd\":\"settings.get\"," + "\"session\":\"%s\",\"org\":%d}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK(jbool(d, "result.smtp_password_set")); + CHECK(jget(d, "result.smtp_password") == NULL); + yyjson_doc_free(d); + + secret_stored = db_text(g_db, reqf("SELECT value FROM settings WHERE" + " org_id=%d AND key='smtp_password'", + (int)org_id)); + CHECK(secret_stored != NULL); + CHECK(secret_stored && strncmp(secret_stored, "enc:v1:", 7) == 0); + CHECK(secret_stored && strstr(secret_stored, "hunter2") == NULL); + char *plain = NULL; + CHECK(secret_stored && secret_decrypt(secret_stored, &plain) == 0); + CHECK(plain && strcmp(plain, "hunter2") == 0); + free(plain); + free(secret_stored); + + char *audit = db_text(g_db, + "SELECT request_json FROM audit_log" + " WHERE action='settings.set'" + " ORDER BY seq DESC LIMIT 1"); + CHECK(audit != NULL); + CHECK(audit && strstr(audit, "\"key\":\"smtp_password\"") != NULL); + CHECK(audit && strstr(audit, "[redacted]") != NULL); + CHECK(audit && strstr(audit, "hunter2") == NULL); + free(audit); + + d = call(reqf("{\"v\":1,\"id\":\"118\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_password\",\"value\":\"\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.value", "[redacted]"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"119\",\"cmd\":\"settings.get\"," + "\"session\":\"%s\",\"org\":%d}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK(!jbool(d, "result.smtp_password_set")); + CHECK(jget(d, "result.smtp_password") == NULL); + yyjson_doc_free(d); + + secret_stored = db_text(g_db, reqf("SELECT value FROM settings WHERE" + " org_id=%d AND key='smtp_password'", + (int)org_id)); + CHECK(secret_stored == NULL); + free(secret_stored); + + audit = db_text(g_db, + "SELECT request_json FROM audit_log" + " WHERE action='settings.set'" + " ORDER BY seq DESC LIMIT 1"); + CHECK(audit && strstr(audit, "\"key\":\"smtp_password\"") != NULL); + CHECK(audit && strstr(audit, "[redacted]") != NULL); + free(audit); + + unsetenv("BOKFD_SECRET_KEY"); + d = call(reqf("{\"v\":1,\"id\":\"120\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"smtp_password\",\"value\":\"\"}}", + g_session, (int)org_id)); + CHECK_STR(d, "error.code", "INTERNAL"); + yyjson_doc_free(d); + unsetenv("BOKFD_SECRET_KEY"); + /* ---------------- bank reconciliation (schema v8) ----------------- */ static const char seb_csv[] = @@ -3042,7 +3348,7 @@ int main(void) CHECK_OK(d); CHECK_STR(d, "result.period", "2026P4"); CHECK_STR(d, "result.blankett", "INK2-2026P4"); - char *sb64 = jstr(d, "result.blanketter.content_base64"); + const char *sb64 = jstr(d, "result.blanketter.content_base64"); unsigned char *srub = NULL; size_t srul = 0; CHECK(sb64 && util_b64_decode(sb64, strlen(sb64), &srub, &srul) == 0); |
