diff --git a/Makefile b/Makefile index a4c03ed..0853ffa 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ WEB_DIR := website CLI_DIR := cli APP_BIN := $(APP_DIR)/vault1984 WEB_BIN := $(WEB_DIR)/vault1984-web -CLI_BIN := $(CLI_DIR)/vault1984 +CLI_BIN := $(CLI_DIR)/v1984 APP_ENTRY := ./cmd/vault1984 WEB_ENTRY := . diff --git a/cli/Makefile b/cli/Makefile index 0be1fcc..c62aada 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -35,7 +35,7 @@ CJSON_DIR := $(VENDOR_DIR)/cjson CRYPTO_DIR := ../crypto # Output binary -BIN := vault1984 +BIN := v1984 # --- Source files --- diff --git a/cli/build/src/keystore.o b/cli/build/src/keystore.o index 7690311..677df93 100644 Binary files a/cli/build/src/keystore.o and b/cli/build/src/keystore.o differ diff --git a/cli/build/src/main.o b/cli/build/src/main.o index e127606..a8c62bd 100644 Binary files a/cli/build/src/main.o and b/cli/build/src/main.o differ diff --git a/cli/src/keystore.c b/cli/src/keystore.c index 1902e71..225a815 100644 --- a/cli/src/keystore.c +++ b/cli/src/keystore.c @@ -2,11 +2,13 @@ * vault1984 CLI — config and key storage * * Files stored in ~/.vault1984/ with strict permissions: - * config — key=value (host, vault_id) - * token — MCP auth token + * config — key=value (vault_url, username) + * api_key — API authentication key * l2.key — 16-byte L2 encryption key (binary) */ +#define _POSIX_C_SOURCE 200809L + #include "keystore.h" #include "util.h" @@ -27,7 +29,7 @@ static int get_config_dir(char *buf, size_t len) { const char *home = getenv("HOME"); - if (!home) home = getenv("USERPROFILE"); /* Windows */ + if (!home) home = getenv("USERPROFILE"); if (!home) { fprintf(stderr, "error: cannot determine home directory\n"); return -1; @@ -70,58 +72,36 @@ static int read_file(const char *path, char *buf, size_t len) { size_t n = fread(buf, 1, len - 1, f); fclose(f); buf[n] = '\0'; - /* strip trailing newline */ while (n > 0 && (buf[n-1] == '\n' || buf[n-1] == '\r')) buf[--n] = '\0'; return 0; } -int keystore_init(const char *host, const char *agent_token) { +int keystore_init(const char *vault_url, const char *username, const char *api_key) { char dir[512]; if (get_config_dir(dir, sizeof(dir)) != 0) return 1; if (ensure_dir(dir) != 0) return 1; - /* - * For now the agent token is the MCP auth token. - * Future: combined token = base64(mcp_token_bytes || encrypted_l2_key) - * We'll split and unwrap when the combined format is defined. - * - * TODO: when combined token format is finalized: - * 1. base64_decode the agent token - * 2. split at known offset → mcp_token_bytes + wrapped_l2_key - * 3. unwrap L2 key - * 4. store separately - */ + /* Strip trailing slash from vault_url */ + char clean_url[512]; + snprintf(clean_url, sizeof(clean_url), "%s", vault_url); + size_t ulen = strlen(clean_url); + while (ulen > 0 && clean_url[ulen - 1] == '/') clean_url[--ulen] = '\0'; - /* write config */ + /* Write config */ char path[512]; - char config_data[512]; - - /* detect scheme: if host starts with http:// or https://, strip and store */ - const char *actual_host = host; - const char *scheme = "https"; - if (strncmp(host, "https://", 8) == 0) { - actual_host = host + 8; - scheme = "https"; - } else if (strncmp(host, "http://", 7) == 0) { - actual_host = host + 7; - scheme = "http"; - } - /* strip trailing slash */ - char clean_host[256]; - snprintf(clean_host, sizeof(clean_host), "%s", actual_host); - size_t hlen = strlen(clean_host); - while (hlen > 0 && clean_host[hlen - 1] == '/') clean_host[--hlen] = '\0'; - - snprintf(config_data, sizeof(config_data), "host=%s\nscheme=%s\n", clean_host, scheme); + char config_data[1024]; + snprintf(config_data, sizeof(config_data), + "vault_url=%s\nusername=%s\n", clean_url, username); snprintf(path, sizeof(path), "%s/config", dir); if (write_file(path, config_data, strlen(config_data)) != 0) return 1; - /* write token */ - snprintf(path, sizeof(path), "%s/token", dir); - if (write_file(path, agent_token, strlen(agent_token)) != 0) return 1; + /* Write API key */ + snprintf(path, sizeof(path), "%s/api_key", dir); + if (write_file(path, api_key, strlen(api_key)) != 0) return 1; - fprintf(stderr, "vault1984: initialized\n"); - fprintf(stderr, " host: %s://%s:1984\n", scheme, clean_host); + fprintf(stderr, "v1984: initialized\n"); + fprintf(stderr, " vault: %s\n", clean_url); + fprintf(stderr, " user: %s\n", username); fprintf(stderr, " config: %s/\n", dir); return 0; } @@ -132,16 +112,16 @@ int keystore_load(struct v84_config *cfg) { char dir[512]; if (get_config_dir(dir, sizeof(dir)) != 0) return -1; - /* read config */ + /* Read config */ char path[512]; - char config_data[512]; + char config_data[1024]; snprintf(path, sizeof(path), "%s/config", dir); if (read_file(path, config_data, sizeof(config_data)) != 0) { - fprintf(stderr, "error: not initialized. Run: vault1984 init --host --agent \n"); + fprintf(stderr, "error: not initialized. Run: v1984 init --vault --user --key \n"); return -1; } - /* parse config key=value lines */ + /* Parse key=value lines */ char *line = strtok(config_data, "\n"); while (line) { char *eq = strchr(line, '='); @@ -149,25 +129,23 @@ int keystore_load(struct v84_config *cfg) { *eq = '\0'; const char *key = line; const char *val = eq + 1; - if (strcmp(key, "host") == 0) { - snprintf(cfg->host, sizeof(cfg->host), "%s", val); - } else if (strcmp(key, "vault_id") == 0) { - snprintf(cfg->vault_id, sizeof(cfg->vault_id), "%s", val); - } else if (strcmp(key, "scheme") == 0) { - snprintf(cfg->scheme, sizeof(cfg->scheme), "%s", val); + if (strcmp(key, "vault_url") == 0) { + snprintf(cfg->vault_url, sizeof(cfg->vault_url), "%s", val); + } else if (strcmp(key, "username") == 0) { + snprintf(cfg->username, sizeof(cfg->username), "%s", val); } } line = strtok(NULL, "\n"); } - /* read token */ - snprintf(path, sizeof(path), "%s/token", dir); - if (read_file(path, cfg->token, sizeof(cfg->token)) != 0) { - fprintf(stderr, "error: token file missing. Run: vault1984 init --host --agent \n"); + /* Read API key */ + snprintf(path, sizeof(path), "%s/api_key", dir); + if (read_file(path, cfg->api_key, sizeof(cfg->api_key)) != 0) { + fprintf(stderr, "error: api_key missing. Run: v1984 init --vault --user --key \n"); return -1; } - /* read L2 key if present */ + /* Read L2 key if present */ snprintf(path, sizeof(path), "%s/l2.key", dir); FILE *f = fopen(path, "rb"); if (f) { @@ -176,21 +154,10 @@ int keystore_load(struct v84_config *cfg) { cfg->has_l2_key = (n == 16); } - if (!cfg->host[0]) { - fprintf(stderr, "error: config missing host. Run: vault1984 init --host --agent \n"); + if (!cfg->vault_url[0]) { + fprintf(stderr, "error: config missing vault_url. Run: v1984 init --vault --user --key \n"); return -1; } - /* default scheme to https */ - if (!cfg->scheme[0]) { - snprintf(cfg->scheme, sizeof(cfg->scheme), "https"); - } - - /* if vault_id not in config, use placeholder from token */ - if (!cfg->vault_id[0]) { - /* TODO: derive from L2 key once combined token format is defined */ - snprintf(cfg->vault_id, sizeof(cfg->vault_id), "default"); - } - return 0; } diff --git a/cli/src/keystore.h b/cli/src/keystore.h index 0e9d9b6..d5844a7 100644 --- a/cli/src/keystore.h +++ b/cli/src/keystore.h @@ -6,16 +6,15 @@ #define V84_KEYSTORE_H struct v84_config { - char host[256]; /* POP hostname (e.g. use.vault1984.com) */ - char vault_id[16]; /* base64(first 8 bytes of L2 key) */ - char token[256]; /* MCP auth token */ - char scheme[8]; /* "http" or "https" */ - unsigned char l2_key[16]; /* L2 encryption key (16 bytes) */ + char vault_url[512]; /* e.g. https://use.vault1984.com:1984 */ + char username[256]; /* e.g. johan@jongsma.me */ + char api_key[256]; /* API auth key (base64) */ + unsigned char l2_key[16]; /* L2 encryption key (16 bytes, derived) */ int has_l2_key; }; -/* Initialize config: split agent token, derive vault_id, write files */ -int keystore_init(const char *host, const char *agent_token); +/* Initialize config from provided values, write to ~/.vault1984/ */ +int keystore_init(const char *vault_url, const char *username, const char *api_key); /* Load config from ~/.vault1984/ */ int keystore_load(struct v84_config *cfg); diff --git a/cli/src/main.c b/cli/src/main.c index fccadca..3ea6032 100644 --- a/cli/src/main.c +++ b/cli/src/main.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "keystore.h" #include "http.h" @@ -18,61 +19,60 @@ static void usage(void) { fprintf(stderr, - "vault1984 %s — credential access for AI agents\n" + "v1984 %s — credential access for AI agents\n" "\n" "Usage:\n" - " vault1984 init --host --agent \n" - " vault1984 get [--json]\n" - " vault1984 list [filter] [--json]\n" - " vault1984 totp \n" + " v1984 init --vault --user --key \n" + " v1984 get [--json]\n" + " v1984 list [filter] [--json]\n" + " v1984 totp \n" + " v1984 test-totp \n" + " v1984 test-crypto\n" "\n" "Options:\n" " --help Show this help\n" " --version Show version\n" " --json Output as JSON\n" "\n" - "Port is always 1984.\n" - "\n" "Examples:\n" - " vault1984 init --host use.vault1984.com --agent ABCDEF...\n" - " vault1984 get GitHub\n" - " vault1984 totp GitHub\n" - " vault1984 list\n" - " vault1984 list --json\n", + " v1984 init --vault https://use.vault1984.com:1984 --user johan@jongsma.me --key ABCDEF...\n" + " v1984 get twitter.com\n" + " v1984 totp twitter.com\n" + " v1984 list\n" + " v1984 test-totp JBSWY3DPEHPK3PXP\n", VERSION); } /* --- URL builder --- */ static void build_url(char *buf, size_t len, const struct v84_config *cfg, const char *path) { - if (cfg->vault_id[0] && strcmp(cfg->vault_id, "default") != 0) { - snprintf(buf, len, "%s://%s:1984/%s%s", cfg->scheme, cfg->host, cfg->vault_id, path); - } else { - snprintf(buf, len, "%s://%s:1984%s", cfg->scheme, cfg->host, path); - } + snprintf(buf, len, "%s%s", cfg->vault_url, path); } /* --- command handlers --- */ static int cmd_init(int argc, char **argv) { - const char *host = NULL; - const char *agent = NULL; + const char *vault = NULL; + const char *user = NULL; + const char *key = NULL; for (int i = 0; i < argc; i++) { - if (strcmp(argv[i], "--host") == 0 && i + 1 < argc) { - host = argv[++i]; - } else if (strcmp(argv[i], "--agent") == 0 && i + 1 < argc) { - agent = argv[++i]; + if (strcmp(argv[i], "--vault") == 0 && i + 1 < argc) { + vault = argv[++i]; + } else if (strcmp(argv[i], "--user") == 0 && i + 1 < argc) { + user = argv[++i]; + } else if (strcmp(argv[i], "--key") == 0 && i + 1 < argc) { + key = argv[++i]; } } - if (!host || !agent) { - fprintf(stderr, "error: --host and --agent are required\n"); - fprintf(stderr, "usage: vault1984 init --host --agent \n"); + if (!vault || !user || !key) { + fprintf(stderr, "error: --vault, --user, and --key are required\n"); + fprintf(stderr, "usage: v1984 init --vault --user --key \n"); return 1; } - return keystore_init(host, agent); + return keystore_init(vault, user, key); } static int cmd_list(int argc, char **argv) { @@ -102,7 +102,7 @@ static int cmd_list(int argc, char **argv) { } struct v84_response resp; - if (http_get(url, cfg.token, &resp) != 0) { + if (http_get(url, cfg.api_key, &resp) != 0) { fprintf(stderr, "error: request failed\n"); return 1; } @@ -179,7 +179,7 @@ static int cmd_get(int argc, char **argv) { build_url(url, sizeof(url), &cfg, path); struct v84_response resp; - if (http_get(url, cfg.token, &resp) != 0) { + if (http_get(url, cfg.api_key, &resp) != 0) { fprintf(stderr, "error: search request failed\n"); return 1; } @@ -223,7 +223,7 @@ static int cmd_get(int argc, char **argv) { build_url(url, sizeof(url), &cfg, path); cJSON_Delete(results); - if (http_get(url, cfg.token, &resp) != 0) { + if (http_get(url, cfg.api_key, &resp) != 0) { fprintf(stderr, "error: fetch request failed\n"); return 1; } @@ -318,7 +318,7 @@ static int cmd_totp(int argc, char **argv) { build_url(url, sizeof(url), &cfg, path); struct v84_response resp; - if (http_get(url, cfg.token, &resp) != 0) { + if (http_get(url, cfg.api_key, &resp) != 0) { fprintf(stderr, "error: search request failed\n"); return 1; } @@ -361,7 +361,7 @@ static int cmd_totp(int argc, char **argv) { build_url(url, sizeof(url), &cfg, path); cJSON_Delete(results); - if (http_get(url, cfg.token, &resp) != 0) { + if (http_get(url, cfg.api_key, &resp) != 0) { fprintf(stderr, "error: TOTP request failed\n"); return 1; } @@ -632,10 +632,22 @@ int main(int argc, char **argv) { return cmd_test_crypto(); } - if (strcmp(cmd, "test-totp") == 0 && argc > 2) { + if (strcmp(cmd, "test-totp") == 0) { + if (argc < 3) { + fprintf(stderr, "Generate a TOTP code from a base32 seed.\n\n"); + fprintf(stderr, "usage: v1984 test-totp \n\n"); + fprintf(stderr, "example: v1984 test-totp JBSWY3DPEHPK3PXP\n"); + fprintf(stderr, " 482901 expires in 17s\n"); + return 1; + } if (jsbridge_init() != 0) { fprintf(stderr, "error: crypto init failed\n"); return 1; } char *code = jsbridge_totp(argv[2]); - if (code) { printf("%s\n", code); free(code); } + if (code) { + long now = (long)time(NULL); + int remaining = 30 - (int)(now % 30); + printf("%s expires in %ds\n", code, remaining); + free(code); + } else { fprintf(stderr, "error: TOTP generation failed\n"); } jsbridge_cleanup(); return code ? 0 : 1; diff --git a/cli/v1984 b/cli/v1984 new file mode 100755 index 0000000..31eaf41 Binary files /dev/null and b/cli/v1984 differ