chore: auto-commit uncommitted changes
|
|
@ -1,4 +1,4 @@
|
|||
# Clovis — build pipeline
|
||||
# Clavis — build pipeline
|
||||
# FIPS 140-3: BoringCrypto via GOEXPERIMENT=boringcrypto
|
||||
# Requires Go 1.24+ (verified: go1.24.0)
|
||||
#
|
||||
|
|
@ -11,12 +11,12 @@
|
|||
GOEXPERIMENT := boringcrypto
|
||||
export GOEXPERIMENT
|
||||
|
||||
VAULT_DIR := clovis-vault
|
||||
CLI_DIR := clovis-cli
|
||||
CRYPTO_DIR := clovis-crypto
|
||||
VAULT_DIR := clavis-vault
|
||||
CLI_DIR := clavis-cli
|
||||
CRYPTO_DIR := clavis-crypto
|
||||
|
||||
VAULT_BIN := $(VAULT_DIR)/clavitor
|
||||
CLI_BIN := $(CLI_DIR)/clovis-cli
|
||||
CLI_BIN := $(CLI_DIR)/clavis-cli
|
||||
|
||||
VAULT_ENTRY := ./cmd/clavitor
|
||||
|
||||
|
|
@ -64,21 +64,21 @@ verify-fips-vault:
|
|||
# --- process management ---
|
||||
|
||||
stop-vault:
|
||||
@pkill -f './clavitor$$' 2>/dev/null || pkill -f 'clovis-vault/clavitor$$' 2>/dev/null || true
|
||||
@pkill -f './clavitor$$' 2>/dev/null || pkill -f 'clavis-vault/clavitor$$' 2>/dev/null || true
|
||||
@sleep 0.5
|
||||
|
||||
stop: stop-vault
|
||||
|
||||
restart-vault: stop-vault
|
||||
cd $(VAULT_DIR) && set -a && . ./.env && set +a && nohup ./clavitor > /tmp/clovis-vault.log 2>&1 &
|
||||
cd $(VAULT_DIR) && set -a && . ./.env && set +a && nohup ./clavitor > /tmp/clavis-vault.log 2>&1 &
|
||||
@sleep 1
|
||||
@ss -tlnp | grep -q ':1984' && echo "vault running on :1984 ✓" || { echo "vault failed to start ✗"; cat /tmp/clovis-vault.log; exit 1; }
|
||||
@ss -tlnp | grep -q ':1984' && echo "vault running on :1984 ✓" || { echo "vault failed to start ✗"; cat /tmp/clavis-vault.log; exit 1; }
|
||||
|
||||
restart: restart-vault
|
||||
|
||||
status:
|
||||
@echo "--- processes ---"
|
||||
@ps aux | grep -E '(clavitor|clovis)' | grep -v grep || echo "nothing running"
|
||||
@ps aux | grep -E '(clavitor|clavis)' | grep -v grep || echo "nothing running"
|
||||
@echo "--- ports ---"
|
||||
@ss -tlnp | grep -E ':1984' || echo "no vault port open"
|
||||
@echo "--- fips ---"
|
||||
|
|
@ -87,7 +87,7 @@ status:
|
|||
# --- logs ---
|
||||
|
||||
logs-vault:
|
||||
@tail -f /tmp/clovis-vault.log
|
||||
@tail -f /tmp/clavis-vault.log
|
||||
|
||||
# --- clean ---
|
||||
|
||||
|
|
@ -1,28 +1,28 @@
|
|||
# Clovis
|
||||
# Clavis
|
||||
|
||||
Secure vault platform with multi-client support.
|
||||
|
||||
## Architecture
|
||||
|
||||
**Clovis is the vault server.** Everything else is a client that talks to it.
|
||||
**Clavis is the vault server.** Everything else is a client that talks to it.
|
||||
|
||||
## Structure
|
||||
|
||||
### Active Development
|
||||
| Directory | Purpose | Status |
|
||||
|-----------|---------|--------|
|
||||
| `clovis-vault/` | Vault server with embedded UI (Go, FIPS 140-3) | **Active** |
|
||||
| `clovis-crypto/` | JavaScript crypto layer | **Active** |
|
||||
| `clovis-cli/` | CLI for agents | **Active** |
|
||||
| `clovis-chrome/` | Chrome browser extension | **Active** |
|
||||
| `clavis-vault/` | Vault server with embedded UI (Go, FIPS 140-3) | **Active** |
|
||||
| `clavis-crypto/` | JavaScript crypto layer | **Active** |
|
||||
| `clavis-cli/` | CLI for agents | **Active** |
|
||||
| `clavis-chrome/` | Chrome browser extension | **Active** |
|
||||
|
||||
### Planned
|
||||
| Directory | Purpose | Status |
|
||||
|-----------|---------|--------|
|
||||
| `clovis-firefox/` | Firefox browser extension | Announced |
|
||||
| `clovis-safari/` | Safari browser extension | Announced |
|
||||
| `clovis-ios/` | iOS native app | Announced |
|
||||
| `clovis-android/` | Android native app | Announced |
|
||||
| `clavis-firefox/` | Firefox browser extension | Announced |
|
||||
| `clavis-safari/` | Safari browser extension | Announced |
|
||||
| `clavis-ios/` | iOS native app | Announced |
|
||||
| `clavis-android/` | Android native app | Announced |
|
||||
|
||||
## Build
|
||||
|
||||
|
|
@ -38,9 +38,9 @@ make logs-web # Tail web logs
|
|||
## Clients
|
||||
|
||||
The vault supports multiple client types:
|
||||
- **Web**: Built-in UI served by vault (`clovis-vault/`)
|
||||
- **CLI**: Command-line tool for automation/agents (`clovis-cli/`)
|
||||
- **Browser Extension**: Auto-fill and TOTP in Chrome (`clovis-chrome/`)
|
||||
- **Web**: Built-in UI served by vault (`clavis-vault/`)
|
||||
- **CLI**: Command-line tool for automation/agents (`clavis-cli/`)
|
||||
- **Browser Extension**: Auto-fill and TOTP in Chrome (`clavis-chrome/`)
|
||||
- **Mobile**: Native iOS/Android apps (planned)
|
||||
|
||||
## Security
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# clavis-cli
|
||||
|
||||
Pure C CLI for credential access by AI agents. Talks to a Clavitor vault over HTTPS, decrypts L2 fields locally.
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
make # build for host
|
||||
make strip # strip binary
|
||||
make clean # remove artifacts
|
||||
```
|
||||
|
||||
Target: `clavitor-cli` binary, <1MB stripped. Requires: C11 compiler, POSIX (Linux/macOS/FreeBSD/Windows).
|
||||
|
||||
## Architecture
|
||||
|
||||
- **src/main.c** — CLI entry point, argument parsing, commands (get, list, totp, test-crypto, test-roundtrip, eval, test-totp)
|
||||
- **src/http.c** — HTTPS client using BearSSL. Loads system CA certs for TLS validation. Supports plain HTTP fallback.
|
||||
- **src/keystore.c** — Config storage at `~/.config/clavitor/config`. AES-128-GCM encrypted + HMAC-SHA256 signed. Inconvenience barrier only — real security is vault-side.
|
||||
- **src/jsbridge.c** — QuickJS bridge exposing BearSSL crypto primitives to JS. Loads `crypto/crypto.js` and `crypto/totp.js` from `../clavis-crypto/`.
|
||||
- **src/util.c** — Base64 (standard + url-safe), URL encoding.
|
||||
|
||||
## Vendored dependencies
|
||||
|
||||
All in `vendor/`, no system package dependencies:
|
||||
- **BearSSL** — TLS, AES-GCM, HKDF, HMAC, PRNG
|
||||
- **QuickJS** — JS runtime for shared crypto logic
|
||||
- **cJSON** — JSON parsing
|
||||
|
||||
## Crypto design
|
||||
|
||||
Three-tier encryption model:
|
||||
- **L1** — first 8 bytes of L2 key, used as Bearer auth token
|
||||
- **L2** — 16-byte AES-128-GCM key, client-side field encryption/decryption
|
||||
- **L3** — requires hardware key (not handled by CLI)
|
||||
|
||||
JS crypto in `../clavis-crypto/` is the single source of truth for encrypt/decrypt logic. The C code bridges BearSSL primitives into QuickJS so the same JS runs in CLI and browser.
|
||||
|
||||
## Token format
|
||||
|
||||
`--token` value is a base64url-encoded, AES-GCM encrypted blob containing: `vault_host \0 agent_name \0 l2_key_16_bytes`. Decrypted using HKDF-derived key from seed `clavitor-l2-`.
|
||||
|
||||
## Vault communication
|
||||
|
||||
All API calls go to `https://<host>:1984` with `Authorization: Bearer <L1>` and `X-Agent: <agent_name>` headers.
|
||||
|
||||
Endpoints used: `/api/entries`, `/api/search?q=`, `/api/entries/<id>`, `/api/ext/totp/<id>`.
|
||||
|
||||
## Testing
|
||||
|
||||
```
|
||||
./clavitor-cli test-crypto # BearSSL + JS crypto self-tests
|
||||
./clavitor-cli test-totp <seed> # TOTP generation from base32 seed
|
||||
./clavitor-cli test-roundtrip # runs crypto/test_crypto.js
|
||||
```
|
||||
|
|
@ -32,7 +32,7 @@ VENDOR_DIR := vendor
|
|||
BEARSSL_DIR := $(VENDOR_DIR)/bearssl
|
||||
QUICKJS_DIR := $(VENDOR_DIR)/quickjs
|
||||
CJSON_DIR := $(VENDOR_DIR)/cjson
|
||||
CRYPTO_DIR := ../clovis-crypto
|
||||
CRYPTO_DIR := ../clavis-crypto
|
||||
|
||||
# Output binary
|
||||
BIN := clavitor-cli
|
||||
|
|
@ -329,7 +329,7 @@ static int parse_url(const char *url, struct parsed_url *out) {
|
|||
|
||||
/* --- parse HTTP response --- */
|
||||
|
||||
static int parse_response(char *resp_buf, size_t resp_len, struct v84_response *resp) {
|
||||
static int parse_response(char *resp_buf, size_t resp_len, struct clv_response *resp) {
|
||||
if (resp_len < 12 || strncmp(resp_buf, "HTTP/", 5) != 0) {
|
||||
fprintf(stderr, "error: invalid HTTP response\n");
|
||||
free(resp_buf);
|
||||
|
|
@ -362,7 +362,7 @@ static int parse_response(char *resp_buf, size_t resp_len, struct v84_response *
|
|||
/* --- HTTP GET (plain) --- */
|
||||
|
||||
static int http_get_plain(const struct parsed_url *pu, const char *bearer_token,
|
||||
const char *agent_name, struct v84_response *resp) {
|
||||
const char *agent_name, struct clv_response *resp) {
|
||||
SOCKET fd = tcp_connect(pu->host, pu->port);
|
||||
if (fd == INVALID_SOCKET) return -1;
|
||||
|
||||
|
|
@ -401,7 +401,7 @@ static int http_get_plain(const struct parsed_url *pu, const char *bearer_token,
|
|||
/* --- HTTPS GET (BearSSL TLS) --- */
|
||||
|
||||
static int http_get_tls(const struct parsed_url *pu, const char *bearer_token,
|
||||
const char *agent_name, struct v84_response *resp) {
|
||||
const char *agent_name, struct clv_response *resp) {
|
||||
SOCKET fd = tcp_connect(pu->host, pu->port);
|
||||
if (fd == INVALID_SOCKET) return -1;
|
||||
|
||||
|
|
@ -476,7 +476,7 @@ static int http_get_tls(const struct parsed_url *pu, const char *bearer_token,
|
|||
/* --- public API --- */
|
||||
|
||||
int http_get(const char *url, const char *bearer_token, const char *agent_name,
|
||||
struct v84_response *resp) {
|
||||
struct clv_response *resp) {
|
||||
memset(resp, 0, sizeof(*resp));
|
||||
|
||||
struct parsed_url pu;
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
* clavitor CLI — HTTPS client (BearSSL)
|
||||
*/
|
||||
|
||||
#ifndef V84_HTTP_H
|
||||
#define V84_HTTP_H
|
||||
#ifndef CLV_HTTP_H
|
||||
#define CLV_HTTP_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct v84_response {
|
||||
struct clv_response {
|
||||
int status; /* HTTP status code */
|
||||
char *body; /* response body (malloc'd, caller frees) */
|
||||
size_t body_len;
|
||||
|
|
@ -16,6 +16,6 @@ struct v84_response {
|
|||
/* Perform HTTPS GET with Bearer auth + optional X-Agent header.
|
||||
* agent_name can be NULL to omit the header. */
|
||||
int http_get(const char *url, const char *bearer_token, const char *agent_name,
|
||||
struct v84_response *resp);
|
||||
struct clv_response *resp);
|
||||
|
||||
#endif
|
||||
|
|
@ -73,7 +73,7 @@ static JSValue js_random_bytes(JSContext *ctx, JSValueConst this_val,
|
|||
|
||||
uint8_t *buf = malloc((size_t)n);
|
||||
br_hmac_drbg_context drbg;
|
||||
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "vault1984-seed", 14);
|
||||
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "clavitor-seed", 13);
|
||||
|
||||
/* Seed with system RNG */
|
||||
uint8_t seed[32];
|
||||
|
|
@ -105,7 +105,7 @@ static JSValue js_aes_gcm_encrypt(JSContext *ctx, JSValueConst this_val,
|
|||
/* Generate random 12-byte nonce */
|
||||
uint8_t nonce[12];
|
||||
br_hmac_drbg_context drbg;
|
||||
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "vault1984-nonce", 15);
|
||||
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "clavitor-nonce", 14);
|
||||
br_prng_seeder seeder = br_prng_seeder_system(NULL);
|
||||
if (seeder) seeder(&drbg.vtable);
|
||||
br_hmac_drbg_generate(&drbg, nonce, 12);
|
||||
|
|
@ -453,8 +453,8 @@ char *jsbridge_totp(const char *seed_b32) {
|
|||
if (!ctx) return NULL;
|
||||
|
||||
JSValue global = JS_GetGlobalObject(ctx);
|
||||
JSValue v84 = JS_GetPropertyStr(ctx, global, "vault1984");
|
||||
JSValue totp_obj = JS_GetPropertyStr(ctx, v84, "totp");
|
||||
JSValue clv = JS_GetPropertyStr(ctx, global, "clavitor");
|
||||
JSValue totp_obj = JS_GetPropertyStr(ctx, clv, "totp");
|
||||
JSValue fn = JS_GetPropertyStr(ctx, totp_obj, "generate_totp");
|
||||
|
||||
JSValue args[1] = { JS_NewString(ctx, seed_b32) };
|
||||
|
|
@ -479,7 +479,7 @@ char *jsbridge_totp(const char *seed_b32) {
|
|||
JS_FreeValue(ctx, result);
|
||||
JS_FreeValue(ctx, fn);
|
||||
JS_FreeValue(ctx, totp_obj);
|
||||
JS_FreeValue(ctx, v84);
|
||||
JS_FreeValue(ctx, clv);
|
||||
JS_FreeValue(ctx, global);
|
||||
return out;
|
||||
}
|
||||
|
|
@ -513,8 +513,8 @@ char *jsbridge_encrypt_field(const unsigned char *key, size_t key_len,
|
|||
if (!ctx) return NULL;
|
||||
|
||||
JSValue global = JS_GetGlobalObject(ctx);
|
||||
JSValue v84 = JS_GetPropertyStr(ctx, global, "vault1984");
|
||||
JSValue crypto_obj = JS_GetPropertyStr(ctx, v84, "crypto");
|
||||
JSValue clv = JS_GetPropertyStr(ctx, global, "clavitor");
|
||||
JSValue crypto_obj = JS_GetPropertyStr(ctx, clv, "crypto");
|
||||
JSValue fn = JS_GetPropertyStr(ctx, crypto_obj, "encrypt_field");
|
||||
|
||||
JSValue key_arr = js_new_uint8array(ctx, key, key_len);
|
||||
|
|
@ -542,7 +542,7 @@ char *jsbridge_encrypt_field(const unsigned char *key, size_t key_len,
|
|||
JS_FreeValue(ctx, result);
|
||||
JS_FreeValue(ctx, fn);
|
||||
JS_FreeValue(ctx, crypto_obj);
|
||||
JS_FreeValue(ctx, v84);
|
||||
JS_FreeValue(ctx, clv);
|
||||
JS_FreeValue(ctx, global);
|
||||
return out;
|
||||
}
|
||||
|
|
@ -552,8 +552,8 @@ char *jsbridge_decrypt_field(const unsigned char *key, size_t key_len,
|
|||
if (!ctx) return NULL;
|
||||
|
||||
JSValue global = JS_GetGlobalObject(ctx);
|
||||
JSValue v84 = JS_GetPropertyStr(ctx, global, "vault1984");
|
||||
JSValue crypto_obj = JS_GetPropertyStr(ctx, v84, "crypto");
|
||||
JSValue clv = JS_GetPropertyStr(ctx, global, "clavitor");
|
||||
JSValue crypto_obj = JS_GetPropertyStr(ctx, clv, "crypto");
|
||||
JSValue fn = JS_GetPropertyStr(ctx, crypto_obj, "decrypt_field");
|
||||
|
||||
JSValue key_arr = js_new_uint8array(ctx, key, key_len);
|
||||
|
|
@ -581,7 +581,7 @@ char *jsbridge_decrypt_field(const unsigned char *key, size_t key_len,
|
|||
JS_FreeValue(ctx, result);
|
||||
JS_FreeValue(ctx, fn);
|
||||
JS_FreeValue(ctx, crypto_obj);
|
||||
JS_FreeValue(ctx, v84);
|
||||
JS_FreeValue(ctx, clv);
|
||||
JS_FreeValue(ctx, global);
|
||||
return out;
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
* Exposes BearSSL crypto primitives to JavaScript.
|
||||
*/
|
||||
|
||||
#ifndef V84_JSBRIDGE_H
|
||||
#define V84_JSBRIDGE_H
|
||||
#ifndef CLV_JSBRIDGE_H
|
||||
#define CLV_JSBRIDGE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v1984 CLI — config and key storage
|
||||
* clavitor CLI — config and key storage
|
||||
*
|
||||
* Config file format (binary):
|
||||
* [4 bytes] magic "V19\x01"
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
#endif
|
||||
|
||||
/* Config encryption key — derived from a string that also appears in the web UI */
|
||||
static const char CONFIG_SEED[] = "vault1984-l2-";
|
||||
static const char CONFIG_SEED[] = "clavitor-l2-";
|
||||
static const unsigned char CONFIG_MAGIC[4] = { 'V', '1', '9', 0x01 };
|
||||
|
||||
/* Derive 16-byte config encryption key from seed */
|
||||
|
|
@ -74,7 +74,7 @@ static int get_config_dir(char *buf, size_t len) {
|
|||
fprintf(stderr, "error: cannot determine home directory\n");
|
||||
return -1;
|
||||
}
|
||||
snprintf(buf, len, "%s/.config/v1984", home);
|
||||
snprintf(buf, len, "%s/.config/clavitor", home);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ int keystore_init(const char *vault_url, const char *agent_name,
|
|||
/* Generate nonce */
|
||||
unsigned char nonce[12];
|
||||
br_hmac_drbg_context drbg;
|
||||
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "v1984-init", 10);
|
||||
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "clavitor-init", 13);
|
||||
br_prng_seeder seeder = br_prng_seeder_system(NULL);
|
||||
if (seeder) seeder(&drbg.vtable);
|
||||
br_hmac_drbg_generate(&drbg, nonce, 12);
|
||||
|
|
@ -177,14 +177,14 @@ int keystore_init(const char *vault_url, const char *agent_name,
|
|||
memset(enc_key, 0, 16);
|
||||
memset(hmac_key, 0, 16);
|
||||
|
||||
fprintf(stderr, "v1984: initialized\n");
|
||||
fprintf(stderr, "clavitor: initialized\n");
|
||||
fprintf(stderr, " vault: %s\n", vault_url);
|
||||
fprintf(stderr, " agent: %s\n", agent_name);
|
||||
fprintf(stderr, " config: %s/config\n", dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int keystore_load(struct v84_config *cfg) {
|
||||
int keystore_load(struct clv_config *cfg) {
|
||||
memset(cfg, 0, sizeof(*cfg));
|
||||
|
||||
char dir[512];
|
||||
|
|
@ -194,7 +194,7 @@ int keystore_load(struct v84_config *cfg) {
|
|||
snprintf(path, sizeof(path), "%s/config", dir);
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
fprintf(stderr, "error: not initialized. Run: v1984 init\n");
|
||||
fprintf(stderr, "error: not initialized. Run: clavitor init\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v1984 CLI — config and key storage
|
||||
* clavitor CLI — config and key storage
|
||||
*
|
||||
* Config is encrypted with a static key (inconvenience barrier)
|
||||
* and signed with HMAC (tamper detection). Not security theater —
|
||||
|
|
@ -7,22 +7,22 @@
|
|||
* and lockout.
|
||||
*/
|
||||
|
||||
#ifndef V84_KEYSTORE_H
|
||||
#define V84_KEYSTORE_H
|
||||
#ifndef CLV_KEYSTORE_H
|
||||
#define CLV_KEYSTORE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct v84_config {
|
||||
char vault_url[512]; /* e.g. https://use.vault1984.com:1984 */
|
||||
struct clv_config {
|
||||
char vault_url[512]; /* e.g. https://use.clavitor.com:1984 */
|
||||
char agent_name[128]; /* e.g. claude-code-forge */
|
||||
unsigned char l2_key[16]; /* L2 encryption key (16 bytes) */
|
||||
};
|
||||
|
||||
/* Initialize config: encrypt and write to ~/.config/v1984/config */
|
||||
/* Initialize config: encrypt and write to ~/.config/clavitor/config */
|
||||
int keystore_init(const char *vault_url, const char *agent_name,
|
||||
const unsigned char *l2_key, size_t l2_key_len);
|
||||
|
||||
/* Load config: read, verify signature, decrypt */
|
||||
int keystore_load(struct v84_config *cfg);
|
||||
int keystore_load(struct clv_config *cfg);
|
||||
|
||||
#endif
|
||||
|
|
@ -38,11 +38,11 @@ static void usage(void) {
|
|||
|
||||
/* --- URL + auth helpers --- */
|
||||
|
||||
static void build_url(char *buf, size_t len, const struct v84_config *cfg, const char *path) {
|
||||
static void build_url(char *buf, size_t len, const struct clv_config *cfg, const char *path) {
|
||||
snprintf(buf, len, "%s%s", cfg->vault_url, path);
|
||||
}
|
||||
|
||||
static void get_bearer(const struct v84_config *cfg, char *buf, size_t len) {
|
||||
static void get_bearer(const struct clv_config *cfg, char *buf, size_t len) {
|
||||
base64_encode(cfg->l2_key, 8, buf, len); /* L1 = first 8 bytes */
|
||||
}
|
||||
|
||||
|
|
@ -118,8 +118,8 @@ static int cmd_test_crypto(void) {
|
|||
{ /* L2 field roundtrip */
|
||||
char *r = jsbridge_eval(
|
||||
"var k = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);"
|
||||
"var ct = vault1984.crypto.l2_encrypt_field(k, 'abcdef01', 'pw', 'secret');"
|
||||
"var pt = vault1984.crypto.l2_decrypt_field(k, 'abcdef01', 'pw', ct);"
|
||||
"var ct = clavitor.crypto.l2_encrypt_field(k, 'abcdef01', 'pw', 'secret');"
|
||||
"var pt = clavitor.crypto.l2_decrypt_field(k, 'abcdef01', 'pw', ct);"
|
||||
"'l2 roundtrip: ' + (pt === 'secret' ? 'OK' : 'FAIL');");
|
||||
if (r) { fprintf(stderr, " [JS] %s\n", r); free(r); }
|
||||
}
|
||||
|
|
@ -150,7 +150,7 @@ static int cmd_test_crypto(void) {
|
|||
|
||||
/* TOTP RFC 6238 test vector */
|
||||
char *code = jsbridge_eval(
|
||||
"vault1984.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);");
|
||||
"clavitor.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);");
|
||||
if (!code || strcmp(code, "287082") != 0) {
|
||||
fprintf(stderr, "FAIL: TOTP (got %s, expect 287082)\n", code ? code : "null");
|
||||
free(code); jsbridge_cleanup(); return 1;
|
||||
|
|
@ -184,7 +184,7 @@ int main(int argc, char **argv) {
|
|||
jsbridge_cleanup();
|
||||
return 1;
|
||||
}
|
||||
char *r = jsbridge_eval("globalThis._v1984_test_result");
|
||||
char *r = jsbridge_eval("globalThis._clavitor_test_result");
|
||||
if (r) { printf("%s\n", r); }
|
||||
int ok = (r && strstr(r, "FAILED") == NULL);
|
||||
free(r);
|
||||
|
|
@ -241,7 +241,7 @@ int main(int argc, char **argv) {
|
|||
char js_code[2048];
|
||||
snprintf(js_code, sizeof(js_code),
|
||||
"(function() {"
|
||||
" var seed = native_encode_utf8('vault1984-l2-');"
|
||||
" var seed = native_encode_utf8('clavitor-l2-');"
|
||||
" var encKey = native_hkdf_sha256(seed, null, native_encode_utf8('token'), 16);"
|
||||
" var ct = native_base64_decode('%s');"
|
||||
" var pt = native_aes_gcm_decrypt_blob(encKey, ct);"
|
||||
|
|
@ -288,7 +288,7 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
/* Build config from token */
|
||||
struct v84_config cfg;
|
||||
struct clv_config cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
snprintf(cfg.vault_url, sizeof(cfg.vault_url), "https://%s:1984", vault_host);
|
||||
snprintf(cfg.agent_name, sizeof(cfg.agent_name), "%s", agent_name);
|
||||
|
|
@ -314,7 +314,7 @@ int main(int argc, char **argv) {
|
|||
build_url(url, sizeof(url), &cfg, "/api/entries");
|
||||
}
|
||||
|
||||
struct v84_response resp;
|
||||
struct clv_response resp;
|
||||
if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: request failed\n"); return 1; }
|
||||
if (resp.status != 200) { fprintf(stderr, "error: server returned %d\n", resp.status); free(resp.body); return 1; }
|
||||
if (json_output) { printf("%s\n", resp.body); free(resp.body); return 0; }
|
||||
|
|
@ -348,7 +348,7 @@ int main(int argc, char **argv) {
|
|||
snprintf(path, sizeof(path), "/api/search?q=%s", encoded);
|
||||
build_url(url, sizeof(url), &cfg, path);
|
||||
|
||||
struct v84_response resp;
|
||||
struct clv_response resp;
|
||||
if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: search failed\n"); return 1; }
|
||||
if (resp.status != 200) { fprintf(stderr, "error: server returned %d\n", resp.status); free(resp.body); return 1; }
|
||||
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
* clavitor CLI — utility functions
|
||||
*/
|
||||
|
||||
#ifndef V84_UTIL_H
|
||||
#define V84_UTIL_H
|
||||
#ifndef CLV_UTIL_H
|
||||
#define CLV_UTIL_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
|
@ -290,7 +290,7 @@
|
|||
}
|
||||
|
||||
/* Return result string (for QuickJS eval or browser display) */
|
||||
globalThis._v1984_test_result = summary;
|
||||
globalThis._clavitor_test_result = summary;
|
||||
return;
|
||||
}
|
||||
tests[idx](function() { run(idx + 1); });
|
||||
|
|
@ -299,10 +299,10 @@
|
|||
run(0);
|
||||
|
||||
/* For sync environments (QuickJS), result is available immediately */
|
||||
if (typeof globalThis._v1984_test_result !== 'undefined') {
|
||||
if (typeof globalThis._clavitor_test_result !== 'undefined') {
|
||||
/* Used by CLI eval */
|
||||
}
|
||||
})();
|
||||
|
||||
/* Return result for jsbridge_eval */
|
||||
globalThis._v1984_test_result || 'RUNNING (async — check console)';
|
||||
globalThis._clavitor_test_result || 'RUNNING (async — check console)';
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Clavis Firefox Extension
|
||||
|
||||
Browser extension for Firefox.
|
||||
|
||||
**Status:** Planned, not yet implemented.
|
||||
|
||||
This extension will share the core logic with clavis-chrome.
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"sessionId":"43d3cb50-0614-4ad7-aa41-58df69624a39","pid":221635,"acquiredAt":1774423023146}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
GO := /usr/local/go/bin/go
|
||||
BINARY := clavitor
|
||||
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
|
||||
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
DATE := $(shell date -u +%Y%m%d-%H%M)
|
||||
|
||||
LDFLAGS := -s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildDate=$(DATE)
|
||||
|
||||
# Deploy targets
|
||||
REMOTE := clavitor-hq
|
||||
REMOTE_PATH := /opt/clavitor/bin
|
||||
|
||||
export GOFIPS140 := latest
|
||||
|
||||
# --- Build targets ---
|
||||
|
||||
.PHONY: build build-all linux-amd64 linux-arm64 release test clean deploy
|
||||
|
||||
build: linux-amd64
|
||||
|
||||
build-all: linux-amd64 linux-arm64
|
||||
|
||||
linux-amd64:
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
|
||||
$(GO) build -ldflags "$(LDFLAGS)" -o $(BINARY)-linux-amd64 ./cmd/clavitor
|
||||
|
||||
linux-arm64:
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc \
|
||||
$(GO) build -ldflags "$(LDFLAGS)" -o $(BINARY)-linux-arm64 ./cmd/clavitor
|
||||
|
||||
release:
|
||||
./scripts/release.sh
|
||||
|
||||
test:
|
||||
$(GO) test ./...
|
||||
|
||||
clean:
|
||||
rm -f $(BINARY)-linux-amd64 $(BINARY)-linux-arm64
|
||||
|
||||
# --- Deploy ---
|
||||
|
||||
deploy: linux-amd64
|
||||
scp $(BINARY)-linux-amd64 $(REMOTE):/tmp/$(BINARY)-new
|
||||
ssh $(REMOTE) 'sudo systemctl stop clavitor && mv /tmp/$(BINARY)-new $(REMOTE_PATH)/$(BINARY) && chmod +x $(REMOTE_PATH)/$(BINARY) && sudo systemctl start clavitor'
|
||||
@echo "Deployed. Verifying..."
|
||||
@sleep 2
|
||||
@ssh $(REMOTE) 'sudo systemctl is-active clavitor'
|
||||
|
|
@ -53,7 +53,7 @@ One root of trust: your hardware authenticator. One tap unlocks both Credential
|
|||
## Quick start
|
||||
|
||||
```bash
|
||||
go build -o clavitor ./cmd/vault1984/
|
||||
go build -o clavitor ./cmd/clavitor/
|
||||
./clavitor
|
||||
# Open http://localhost:1984/app/
|
||||
# Register a passkey → vault is ready
|
||||
|
|
@ -73,7 +73,7 @@ Create a token in the web UI (Tokens page), then add to your MCP client config:
|
|||
"clavitor": {
|
||||
"url": "http://localhost:1984/mcp",
|
||||
"headers": {
|
||||
"Authorization": "Bearer v1984_..."
|
||||
"Authorization": "Bearer clavitor_..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -21,7 +20,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/johanj/vault1984/lib"
|
||||
"github.com/johanj/clavitor/lib"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
|
|
@ -106,18 +105,8 @@ func (h *Handlers) VaultInfo(w http.ResponseWriter, r *http.Request) {
|
|||
JSONResponse(w, http.StatusOK, map[string]string{"vault_id": vaultID})
|
||||
}
|
||||
|
||||
// Version is derived from the binary's modification time.
|
||||
var Version = func() string {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "dev"
|
||||
}
|
||||
info, err := os.Stat(exe)
|
||||
if err != nil {
|
||||
return "dev"
|
||||
}
|
||||
return info.ModTime().UTC().Format("20060102-1504")
|
||||
}()
|
||||
// Version is set by main via ldflags at build time.
|
||||
var Version = "dev"
|
||||
|
||||
// Health returns server status.
|
||||
func (h *Handlers) Health(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -221,7 +210,7 @@ func (h *Handlers) AuthRegisterBegin(w http.ResponseWriter, r *http.Request) {
|
|||
"challenge": challengeBytes,
|
||||
"rp": map[string]string{"name": "Clavitor", "id": rpID(r)},
|
||||
"user": map[string]any{
|
||||
"id": []byte("vault1984-owner"),
|
||||
"id": []byte("clavitor-owner"),
|
||||
"name": "vault-owner",
|
||||
"displayName": "Vault Owner",
|
||||
},
|
||||
|
|
@ -272,14 +261,14 @@ func (h *Handlers) AuthRegisterComplete(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
// First passkey → create DB
|
||||
if db == nil && len(req.PublicKey) > 0 {
|
||||
// DB named from L1 key: vault1984-XXXXXX (base64url of first 4 bytes, no extension)
|
||||
// DB named from L1 key: clavitor-XXXXXX (base64url of first 4 bytes, no extension)
|
||||
var dbName string
|
||||
if len(req.L1Key) >= 4 {
|
||||
dbName = "vault1984-" + base64UrlEncode(req.L1Key[:4])
|
||||
dbName = "clavitor-" + base64UrlEncode(req.L1Key[:4])
|
||||
} else {
|
||||
// Fallback: derive from public key hash (legacy compat)
|
||||
// Fallback: derive from public key hash
|
||||
hash := sha256.Sum256(req.PublicKey)
|
||||
dbName = "vault1984-" + base64UrlEncode(hash[:4])
|
||||
dbName = "clavitor-" + base64UrlEncode(hash[:4])
|
||||
}
|
||||
dbPath := filepath.Join(h.Cfg.DataDir, dbName)
|
||||
newDB, err := lib.OpenDB(dbPath)
|
||||
|
|
@ -1699,7 +1688,7 @@ func (h *Handlers) HandleWebAuthnRegisterBegin(w http.ResponseWriter, r *http.Re
|
|||
"challenge": challenge,
|
||||
"rp": map[string]string{"name": "Clavitor", "id": rpID(r)},
|
||||
"user": map[string]any{
|
||||
"id": []byte("vault1984-owner"),
|
||||
"id": []byte("clavitor-owner"),
|
||||
"name": "vault-owner",
|
||||
"displayName": "Clavitor Owner",
|
||||
},
|
||||
|
|
@ -19,7 +19,7 @@ import (
|
|||
"testing"
|
||||
"embed"
|
||||
|
||||
"github.com/johanj/vault1984/lib"
|
||||
"github.com/johanj/clavitor/lib"
|
||||
)
|
||||
|
||||
// --- test helpers ---
|
||||
|
|
@ -13,7 +13,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/johanj/vault1984/lib"
|
||||
"github.com/johanj/clavitor/lib"
|
||||
)
|
||||
|
||||
// base64Decode handles both standard and url-safe base64 (with or without padding).
|
||||
|
|
@ -93,7 +93,7 @@ func VaultIDFromContext(ctx context.Context) int64 {
|
|||
// Fully stateless: L1 arrives with every request, is used, then forgotten.
|
||||
// No sessions, no stored keys. The server has zero keys of its own.
|
||||
//
|
||||
// Self-hosted mode: finds vault DB by globbing vault1984-* files.
|
||||
// Self-hosted mode: finds vault DB by globbing clavitor-* files.
|
||||
// Hosted mode: finds vault DB by base64url(L1[0:4]) → filename.
|
||||
func L1Middleware(dataDir string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
|
|
@ -103,7 +103,7 @@ func L1Middleware(dataDir string) func(http.Handler) http.Handler {
|
|||
// No auth = unauthenticated request (registration, login begin, etc.)
|
||||
if auth == "" || !strings.HasPrefix(auth, "Bearer ") {
|
||||
// Try to open vault DB without L1 (for unauthenticated endpoints)
|
||||
matches, _ := filepath.Glob(filepath.Join(dataDir, "vault1984-*"))
|
||||
matches, _ := filepath.Glob(filepath.Join(dataDir, "clavitor-*"))
|
||||
if len(matches) > 0 {
|
||||
db, err := lib.OpenDB(matches[0])
|
||||
if err == nil {
|
||||
|
|
@ -141,7 +141,7 @@ func L1Middleware(dataDir string) func(http.Handler) http.Handler {
|
|||
|
||||
// Find vault DB by first 4 bytes of L1
|
||||
vaultPrefix := base64UrlEncode(l1Raw[:4])
|
||||
dbPath := filepath.Join(dataDir, "vault1984-"+vaultPrefix)
|
||||
dbPath := filepath.Join(dataDir, "clavitor-"+vaultPrefix)
|
||||
log.Printf("L1 auth: l1_hex=%x prefix=%s path=%s", l1Raw, vaultPrefix, dbPath)
|
||||
|
||||
var db *lib.DB
|
||||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/johanj/vault1984/lib"
|
||||
"github.com/johanj/clavitor/lib"
|
||||
)
|
||||
|
||||
// NewRouter creates the main router with all routes registered.
|
||||
|
|
@ -93,7 +93,7 @@ func NewRouter(cfg *lib.Config, webFS embed.FS, templateFS embed.FS) *chi.Mux {
|
|||
w.Write(data)
|
||||
}
|
||||
}
|
||||
r.Get("/vault1984.css", serveEmbedded("vault1984.css", "text/css; charset=utf-8"))
|
||||
r.Get("/clavitor.css", serveEmbedded("clavitor.css", "text/css; charset=utf-8"))
|
||||
r.Get("/worldmap.svg", serveEmbedded("worldmap.svg", "image/svg+xml"))
|
||||
r.Get("/favicon.svg", serveEmbedded("favicon.svg", "image/svg+xml"))
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/johanj/vault1984/lib"
|
||||
"github.com/johanj/clavitor/lib"
|
||||
)
|
||||
|
||||
/*
|
||||
|
|
@ -261,8 +261,8 @@ func TestTierIsolationDB(t *testing.T) {
|
|||
func TestCLICrypto(t *testing.T) {
|
||||
// Find CLI binary via absolute path
|
||||
home := os.Getenv("HOME")
|
||||
cliBin := home + "/dev/clavitor/clovis/clovis-cli/clovis-cli"
|
||||
cliDir := home + "/dev/clavitor/clovis/clovis-cli"
|
||||
cliBin := home + "/dev/clavitor/clavis/clavis-cli/clavis-cli"
|
||||
cliDir := home + "/dev/clavitor/clavis/clavis-cli"
|
||||
if _, err := os.Stat(cliBin); err != nil {
|
||||
t.Skip("clavitor-cli not found — run 'make cli' first")
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ import (
|
|||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/johanj/vault1984/api"
|
||||
"github.com/johanj/vault1984/lib"
|
||||
"github.com/johanj/clavitor/api"
|
||||
"github.com/johanj/clavitor/lib"
|
||||
)
|
||||
|
||||
//go:embed web
|
||||
|
|
@ -18,7 +18,15 @@ var webFS embed.FS
|
|||
//go:embed templates
|
||||
var templateFS embed.FS
|
||||
|
||||
// Set via -ldflags at build time.
|
||||
var (
|
||||
version = "dev"
|
||||
commit = "unknown"
|
||||
buildDate = "unknown"
|
||||
)
|
||||
|
||||
func main() {
|
||||
api.Version = version + " (" + commit + " " + buildDate + ")"
|
||||
// Telemetry flags (all optional — without them, no telemetry runs).
|
||||
telemetryFreq := flag.Int("telemetry-freq", envInt("TELEMETRY_FREQ", 0), "Telemetry POST interval in seconds (0 = disabled)")
|
||||
telemetryHost := flag.String("telemetry-host", envStr("TELEMETRY_HOST", ""), "Telemetry endpoint URL")
|
||||
|
|
@ -37,6 +45,7 @@ func main() {
|
|||
Token: *telemetryToken,
|
||||
DataDir: cfg.DataDir,
|
||||
Mode: cfg.Mode,
|
||||
Version: version,
|
||||
})
|
||||
|
||||
// Start automatic backup scheduler (3 weekly + 3 monthly, rotated)
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
<h3 class="mb-4">2. Connect Claude Code</h3>
|
||||
<p class="mb-4">In your terminal, run:</p>
|
||||
<div class="code-block">claude mcp add clavitor --transport http --url http://localhost:1984/mcp \
|
||||
--header "Authorization: Bearer v1984_your_token_here"</div>
|
||||
--header "Authorization: Bearer clavitor_your_token_here"</div>
|
||||
<p class="mt-4" style="font-size:0.8125rem;color:var(--muted)">That’s it. Claude Code picks up the new server automatically.</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
<div class="code-block mb-4"><pre>Name: clavitor
|
||||
URL: http://localhost:1984/mcp</pre></div>
|
||||
<p class="mb-4">Click <strong>Add</strong>, then expand the entry and add a header:</p>
|
||||
<div class="code-block"><pre>Authorization: Bearer v1984_your_token_here</pre></div>
|
||||
<div class="code-block"><pre>Authorization: Bearer clavitor_your_token_here</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-6" style="border-color:var(--border-gold)">
|
||||
|
|
@ -49,14 +49,14 @@
|
|||
url = "http://localhost:1984/mcp"
|
||||
|
||||
[mcp_servers.clavitor.headers]
|
||||
Authorization = "Bearer v1984_your_token_here"</pre></div>
|
||||
Authorization = "Bearer clavitor_your_token_here"</pre></div>
|
||||
</div>
|
||||
<div class="card mb-6">
|
||||
<span class="badge mb-4">Option B</span>
|
||||
<h3 class="mb-4">REST API + Function Calling</h3>
|
||||
<p class="mb-4">Define <span class="vaultname">clav<span class="n">itor</span></span> endpoints as functions. Works with any LLM that supports function calling.</p>
|
||||
<div class="code-block"><pre>curl http://localhost:1984/api/search?q=github \
|
||||
-H "Authorization: Bearer v1984_your_token_here"
|
||||
-H "Authorization: Bearer clavitor_your_token_here"
|
||||
|
||||
# Returns entries with credentials, URLs, TOTP codes
|
||||
# Personal fields return: {"value":"[REDACTED]","l2":true}</pre></div>
|
||||
|
|
@ -82,7 +82,7 @@ GET /api/ext/totp/{id} # get live TOTP code
|
|||
GET /api/generate?length=32 # generate random password</pre></div>
|
||||
</div>
|
||||
|
||||
<p style="font-size:0.875rem;color:var(--muted)">All endpoints require <code>Authorization: Bearer v1984_...</code></p>
|
||||
<p style="font-size:0.875rem;color:var(--muted)">All endpoints require <code>Authorization: Bearer clavitor_...</code></p>
|
||||
</div>
|
||||
|
||||
<div class="section container">
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
<h3 class="mb-4">2. 配置令牌</h3>
|
||||
<p class="mb-4">在 <span class="vaultname">clav<span class="n">itor</span></span> 网页界面创建令牌,然后配置:</p>
|
||||
<div class="code-block"><pre>claw config set clavitor.url "http://localhost:1984/mcp"
|
||||
claw config set clavitor.token "v1984_your_token_here"</pre></div>
|
||||
claw config set clavitor.token "clavitor_your_token_here"</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-6">
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
<h3 class="mb-4">2. Configure your token</h3>
|
||||
<p class="mb-4">Create a token in the <span class="vaultname">clav<span class="n">itor</span></span> web UI, then set it in your OpenClaw config:</p>
|
||||
<div class="code-block"><pre>claw config set clavitor.url "http://localhost:1984/mcp"
|
||||
claw config set clavitor.token "v1984_your_token_here"</pre></div>
|
||||
claw config set clavitor.token "clavitor_your_token_here"</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-6">
|
||||
|
|
@ -147,7 +147,7 @@ function normalize_key(key) {
|
|||
}
|
||||
|
||||
function encrypt_field(key, field_label, plaintext) {
|
||||
var info_str = 'vault1984-field-' + field_label;
|
||||
var info_str = 'clavitor-field-' + field_label;
|
||||
var nkey = normalize_key(key);
|
||||
var aes_len = nkey.length; /* 16 or 32 */
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ function encrypt_field(key, field_label, plaintext) {
|
|||
* @returns {string|Promise<string>} plaintext
|
||||
*/
|
||||
function decrypt_field(key, field_label, ciphertext_b64) {
|
||||
var info_str = 'vault1984-field-' + field_label;
|
||||
var info_str = 'clavitor-field-' + field_label;
|
||||
var nkey = normalize_key(key);
|
||||
var aes_len = nkey.length;
|
||||
|
||||
|
|
@ -205,8 +205,8 @@ function l2_encrypt_field(key, entry_id, label, pt) { return encrypt_field(key,
|
|||
function l2_decrypt_field(key, entry_id, label, ct) { return decrypt_field(key, label, ct); }
|
||||
|
||||
/* Export for both environments */
|
||||
if (typeof globalThis.vault1984 === 'undefined') globalThis.vault1984 = {};
|
||||
globalThis.vault1984.crypto = {
|
||||
if (typeof globalThis.clavitor === 'undefined') globalThis.clavitor = {};
|
||||
globalThis.clavitor.crypto = {
|
||||
aes_gcm_encrypt: aes_gcm_encrypt,
|
||||
aes_gcm_decrypt: aes_gcm_decrypt,
|
||||
hkdf_sha256: hkdf_sha256,
|
||||
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 355 B |
|
|
@ -31,7 +31,7 @@
|
|||
var BUILD = '20260321-stateless';
|
||||
console.log('clavitor build: ' + BUILD);
|
||||
|
||||
// Stateless auth: no token variable. Auth state = sessionStorage has v1984_master.
|
||||
// Stateless auth: no token variable. Auth state = sessionStorage has clavitor_master.
|
||||
var entries = [];
|
||||
var currentEntry = null;
|
||||
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
|
||||
async function init() {
|
||||
// Stateless: either we have the master key from a PRF tap, or we don't.
|
||||
if (sessionStorage.getItem('v1984_master')) {
|
||||
if (sessionStorage.getItem('clavitor_master')) {
|
||||
restoreAppLayout();
|
||||
loadEntries(true);
|
||||
return;
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
|
||||
// isAdditional: true when adding another passkey after first registration
|
||||
function showDevicePicker(isAdditional) {
|
||||
var heading = isAdditional ? 'Add another passkey' : 'Welcome to <span class="vaultname">vault<span class="n">1984</span></span>';
|
||||
var heading = isAdditional ? 'Add another passkey' : 'Welcome to <span class="vaultname">clav<span class="n">itor</span></span>';
|
||||
var sub = isAdditional
|
||||
? 'Register a backup device. You can always add more later from Security settings.'
|
||||
: 'Your personal password vault. No accounts, no cloud. Just you and your passkey.';
|
||||
|
|
@ -334,7 +334,7 @@
|
|||
document.getElementById('app').innerHTML =
|
||||
'<div class="unlock-wrap">' +
|
||||
'<div class="unlock-inner">' +
|
||||
'<div class="vaultname" style="font-size:2rem;margin-bottom:1rem">vault<span class="n">1984</span></div>' +
|
||||
'<div class="vaultname" style="font-size:2rem;margin-bottom:1rem">clav<span class="n">itor</span></div>' +
|
||||
'<h2 class="mb-2">Unlock Vault</h2>' +
|
||||
'<p class="mb-8">Authenticate with your passkey to access your vault.</p>' +
|
||||
'<div id="unlockError" class="alert alert-error hidden mb-4"></div>' +
|
||||
|
|
@ -402,7 +402,7 @@
|
|||
await ClavitorWebAuthn.storeMasterKey(extResults.prf.results.first);
|
||||
}
|
||||
|
||||
if (!sessionStorage.getItem('v1984_master')) {
|
||||
if (!sessionStorage.getItem('clavitor_master')) {
|
||||
throw new Error('PRF output not available — cannot derive encryption keys');
|
||||
}
|
||||
console.log('clavitor: L1 bearer =', getL1Bearer());
|
||||
|
|
@ -466,7 +466,7 @@
|
|||
}
|
||||
|
||||
function lockVault() {
|
||||
sessionStorage.removeItem('v1984_master');
|
||||
sessionStorage.removeItem('clavitor_master');
|
||||
showUnlock();
|
||||
}
|
||||
|
||||
|
|
@ -482,7 +482,7 @@
|
|||
} catch (e) {
|
||||
if (e.message !== 'Unauthorized') {
|
||||
// Vault not found (deleted/new device) — clear stale session
|
||||
sessionStorage.removeItem('v1984_master');
|
||||
sessionStorage.removeItem('clavitor_master');
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
|
@ -1781,7 +1781,7 @@
|
|||
try {
|
||||
var info = await fetch('/health').then(function(r) { return r.json(); });
|
||||
var html = '<div class="modal-body" style="text-align:center">' +
|
||||
'<div class="vaultname" style="font-size:2.5rem;margin-bottom:0.5rem">vault<span class="n">1984</span></div>' +
|
||||
'<div class="vaultname" style="font-size:2.5rem;margin-bottom:0.5rem">clav<span class="n">itor</span></div>' +
|
||||
'<p class="text-subtle mb-6">Password manager for humans with AI assistants</p>' +
|
||||
'<div style="display:inline-block;text-align:left;font-size:0.8125rem;line-height:2">' +
|
||||
'<div><span class="text-subtle">Version</span> <strong>' + escapeHtml(info.version) + '</strong></div>' +
|
||||
|
|
@ -55,9 +55,9 @@
|
|||
/* --- Test 1: L1 (8-byte) roundtrip --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'L1 (8B) encrypt/decrypt roundtrip';
|
||||
resolve(vault1984.crypto.encrypt_field(K8, 'username', 'johanj'), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K8, 'username', 'johanj'), function(ct, err) {
|
||||
if (err) { fail(name, err.message); done(); return; }
|
||||
resolve(vault1984.crypto.decrypt_field(K8, 'username', ct), function(pt, err2) {
|
||||
resolve(clavitor.crypto.decrypt_field(K8, 'username', ct), function(pt, err2) {
|
||||
if (err2) fail(name, err2.message);
|
||||
else if (pt === 'johanj') pass(name);
|
||||
else fail(name, 'got "' + pt + '"');
|
||||
|
|
@ -69,9 +69,9 @@
|
|||
/* --- Test 2: L2 (16-byte) roundtrip --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'L2 (16B) encrypt/decrypt roundtrip';
|
||||
resolve(vault1984.crypto.encrypt_field(K16, 'password', 's3cret!P@ss'), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K16, 'password', 's3cret!P@ss'), function(ct, err) {
|
||||
if (err) { fail(name, err.message); done(); return; }
|
||||
resolve(vault1984.crypto.decrypt_field(K16, 'password', ct), function(pt, err2) {
|
||||
resolve(clavitor.crypto.decrypt_field(K16, 'password', ct), function(pt, err2) {
|
||||
if (err2) fail(name, err2.message);
|
||||
else if (pt === 's3cret!P@ss') pass(name);
|
||||
else fail(name, 'got "' + pt + '"');
|
||||
|
|
@ -83,9 +83,9 @@
|
|||
/* --- Test 3: L3 (32-byte) roundtrip --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'L3 (32B) encrypt/decrypt roundtrip';
|
||||
resolve(vault1984.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
|
||||
if (err) { fail(name, err.message); done(); return; }
|
||||
resolve(vault1984.crypto.decrypt_field(K32, 'passport', ct), function(pt, err2) {
|
||||
resolve(clavitor.crypto.decrypt_field(K32, 'passport', ct), function(pt, err2) {
|
||||
if (err2) fail(name, err2.message);
|
||||
else if (pt === 'NL12345678') pass(name);
|
||||
else fail(name, 'got "' + pt + '"');
|
||||
|
|
@ -97,9 +97,9 @@
|
|||
/* --- Test 4: L2 key cannot decrypt L3 ciphertext --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'L2 key rejects L3 ciphertext';
|
||||
resolve(vault1984.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
|
||||
if (err) { fail(name, 'encrypt failed: ' + err.message); done(); return; }
|
||||
safe(function() { return vault1984.crypto.decrypt_field(K16, 'passport', ct); }, function(pt, err2) {
|
||||
safe(function() { return clavitor.crypto.decrypt_field(K16, 'passport', ct); }, function(pt, err2) {
|
||||
if (err2) pass(name);
|
||||
else fail(name, 'L2 key decrypted L3 data to "' + pt + '"');
|
||||
done();
|
||||
|
|
@ -110,9 +110,9 @@
|
|||
/* --- Test 5: L3 key cannot decrypt L2 ciphertext --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'L3 key rejects L2 ciphertext';
|
||||
resolve(vault1984.crypto.encrypt_field(K16, 'password', 'secret'), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K16, 'password', 'secret'), function(ct, err) {
|
||||
if (err) { fail(name, 'encrypt failed: ' + err.message); done(); return; }
|
||||
safe(function() { return vault1984.crypto.decrypt_field(K32, 'password', ct); }, function(pt, err2) {
|
||||
safe(function() { return clavitor.crypto.decrypt_field(K32, 'password', ct); }, function(pt, err2) {
|
||||
if (err2) pass(name);
|
||||
else fail(name, 'L3 key decrypted L2 data to "' + pt + '"');
|
||||
done();
|
||||
|
|
@ -123,9 +123,9 @@
|
|||
/* --- Test 6: Wrong key rejection --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'wrong key rejected';
|
||||
resolve(vault1984.crypto.encrypt_field(K16, 'secret', 'value'), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K16, 'secret', 'value'), function(ct, err) {
|
||||
if (err) { fail(name, 'encrypt failed'); done(); return; }
|
||||
safe(function() { return vault1984.crypto.decrypt_field(WRONG_K16, 'secret', ct); }, function(pt, err2) {
|
||||
safe(function() { return clavitor.crypto.decrypt_field(WRONG_K16, 'secret', ct); }, function(pt, err2) {
|
||||
if (err2) pass(name);
|
||||
else fail(name, 'wrong key decrypted to "' + pt + '"');
|
||||
done();
|
||||
|
|
@ -136,9 +136,9 @@
|
|||
/* --- Test 7: Wrong label rejection --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'wrong label rejected';
|
||||
resolve(vault1984.crypto.encrypt_field(K16, 'labelA', 'value'), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K16, 'labelA', 'value'), function(ct, err) {
|
||||
if (err) { fail(name, 'encrypt failed'); done(); return; }
|
||||
safe(function() { return vault1984.crypto.decrypt_field(K16, 'labelB', ct); }, function(pt, err2) {
|
||||
safe(function() { return clavitor.crypto.decrypt_field(K16, 'labelB', ct); }, function(pt, err2) {
|
||||
if (err2) pass(name);
|
||||
else fail(name, 'wrong label decrypted to "' + pt + '"');
|
||||
done();
|
||||
|
|
@ -149,9 +149,9 @@
|
|||
/* --- Test 8: Empty string roundtrip --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'empty string roundtrip';
|
||||
resolve(vault1984.crypto.encrypt_field(K16, 'empty', ''), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K16, 'empty', ''), function(ct, err) {
|
||||
if (err) { fail(name, err.message); done(); return; }
|
||||
resolve(vault1984.crypto.decrypt_field(K16, 'empty', ct), function(pt, err2) {
|
||||
resolve(clavitor.crypto.decrypt_field(K16, 'empty', ct), function(pt, err2) {
|
||||
if (err2) fail(name, err2.message);
|
||||
else if (pt === '') pass(name);
|
||||
else fail(name, 'got "' + pt + '"');
|
||||
|
|
@ -164,9 +164,9 @@
|
|||
tests.push(function(done) {
|
||||
var name = 'unicode roundtrip';
|
||||
var unicode = 'pässwörd 密码 🔑';
|
||||
resolve(vault1984.crypto.encrypt_field(K16, 'intl', unicode), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K16, 'intl', unicode), function(ct, err) {
|
||||
if (err) { fail(name, err.message); done(); return; }
|
||||
resolve(vault1984.crypto.decrypt_field(K16, 'intl', ct), function(pt, err2) {
|
||||
resolve(clavitor.crypto.decrypt_field(K16, 'intl', ct), function(pt, err2) {
|
||||
if (err2) fail(name, err2.message);
|
||||
else if (pt === unicode) pass(name);
|
||||
else fail(name, 'mismatch');
|
||||
|
|
@ -182,9 +182,9 @@
|
|||
* K16 = [11,22,33,44,55,66,77,88,99,110,111,112,113,114,115,116]
|
||||
* These are different keys after normalization, so HKDF produces different field keys.
|
||||
* L1 ciphertext must NOT be decryptable with L2 key. */
|
||||
resolve(vault1984.crypto.encrypt_field(K8, 'field', 'test'), function(ct1, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K8, 'field', 'test'), function(ct1, err) {
|
||||
if (err) { fail(name, 'L1 encrypt: ' + err.message); done(); return; }
|
||||
safe(function() { return vault1984.crypto.decrypt_field(K16, 'field', ct1); }, function(pt, err2) {
|
||||
safe(function() { return clavitor.crypto.decrypt_field(K16, 'field', ct1); }, function(pt, err2) {
|
||||
if (err2) pass(name);
|
||||
else fail(name, 'L2 key decrypted L1 data');
|
||||
done();
|
||||
|
|
@ -195,7 +195,7 @@
|
|||
/* --- Test 11: TOTP generation (RFC 6238 test vector) --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'TOTP RFC 6238 test vector';
|
||||
var result = vault1984.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);
|
||||
var result = clavitor.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);
|
||||
resolve(result, function(code, err) {
|
||||
if (err) fail(name, err.message);
|
||||
else if (code === '287082') pass(name);
|
||||
|
|
@ -207,7 +207,7 @@
|
|||
/* --- Test 12: TOTP second test vector (time=1111111109) --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'TOTP RFC 6238 test vector #2';
|
||||
var result = vault1984.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 1111111109, 30, 8);
|
||||
var result = clavitor.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 1111111109, 30, 8);
|
||||
resolve(result, function(code, err) {
|
||||
if (err) fail(name, err.message);
|
||||
else if (code === '07081804') pass(name);
|
||||
|
|
@ -220,10 +220,10 @@
|
|||
tests.push(function(done) {
|
||||
var name = 'L3 TOTP seed inaccessible to agent (L2 key)';
|
||||
var seed = 'JBSWY3DPEHPK3PXP'; // a TOTP seed
|
||||
resolve(vault1984.crypto.encrypt_field(K32, 'totp', seed), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K32, 'totp', seed), function(ct, err) {
|
||||
if (err) { fail(name, 'encrypt failed'); done(); return; }
|
||||
// Agent has K16 (L2), tries to decrypt L3 TOTP seed
|
||||
safe(function() { return vault1984.crypto.decrypt_field(K16, 'totp', ct); }, function(pt, err2) {
|
||||
safe(function() { return clavitor.crypto.decrypt_field(K16, 'totp', ct); }, function(pt, err2) {
|
||||
if (err2) pass(name);
|
||||
else fail(name, 'L2 key decrypted L3 TOTP seed to "' + pt + '"');
|
||||
done();
|
||||
|
|
@ -235,9 +235,9 @@
|
|||
tests.push(function(done) {
|
||||
var name = 'L2 TOTP seed accessible to agent (L2 key)';
|
||||
var seed = 'JBSWY3DPEHPK3PXP';
|
||||
resolve(vault1984.crypto.encrypt_field(K16, 'totp', seed), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K16, 'totp', seed), function(ct, err) {
|
||||
if (err) { fail(name, 'encrypt failed'); done(); return; }
|
||||
resolve(vault1984.crypto.decrypt_field(K16, 'totp', ct), function(pt, err2) {
|
||||
resolve(clavitor.crypto.decrypt_field(K16, 'totp', ct), function(pt, err2) {
|
||||
if (err2) fail(name, err2.message);
|
||||
else if (pt === seed) pass(name);
|
||||
else fail(name, 'got "' + pt + '"');
|
||||
|
|
@ -249,9 +249,9 @@
|
|||
/* --- Test 15: L3 card number cannot be read with L2 key --- */
|
||||
tests.push(function(done) {
|
||||
var name = 'L3 card number inaccessible to agent (L2 key)';
|
||||
resolve(vault1984.crypto.encrypt_field(K32, 'Number', '5452120017212208'), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K32, 'Number', '5452120017212208'), function(ct, err) {
|
||||
if (err) { fail(name, 'encrypt failed'); done(); return; }
|
||||
safe(function() { return vault1984.crypto.decrypt_field(K16, 'Number', ct); }, function(pt, err2) {
|
||||
safe(function() { return clavitor.crypto.decrypt_field(K16, 'Number', ct); }, function(pt, err2) {
|
||||
if (err2) pass(name);
|
||||
else fail(name, 'L2 key decrypted L3 card number');
|
||||
done();
|
||||
|
|
@ -263,9 +263,9 @@
|
|||
tests.push(function(done) {
|
||||
var name = 'truncation: L2 prefix of L3, still cannot decrypt L3';
|
||||
// K16 = K32[0..16] by design. Encrypt with full K32, try with K16.
|
||||
resolve(vault1984.crypto.encrypt_field(K32, 'secret', 'classified'), function(ct, err) {
|
||||
resolve(clavitor.crypto.encrypt_field(K32, 'secret', 'classified'), function(ct, err) {
|
||||
if (err) { fail(name, 'encrypt failed'); done(); return; }
|
||||
safe(function() { return vault1984.crypto.decrypt_field(K16, 'secret', ct); }, function(pt, err2) {
|
||||
safe(function() { return clavitor.crypto.decrypt_field(K16, 'secret', ct); }, function(pt, err2) {
|
||||
if (err2) pass(name);
|
||||
else fail(name, 'L2 (prefix of L3) decrypted L3 data');
|
||||
done();
|
||||
|
|
@ -290,7 +290,7 @@
|
|||
}
|
||||
|
||||
/* Return result string (for QuickJS eval or browser display) */
|
||||
globalThis._v1984_test_result = summary;
|
||||
globalThis._clavitor_test_result = summary;
|
||||
return;
|
||||
}
|
||||
tests[idx](function() { run(idx + 1); });
|
||||
|
|
@ -299,10 +299,10 @@
|
|||
run(0);
|
||||
|
||||
/* For sync environments (QuickJS), result is available immediately */
|
||||
if (typeof globalThis._v1984_test_result !== 'undefined') {
|
||||
if (typeof globalThis._clavitor_test_result !== 'undefined') {
|
||||
/* Used by CLI eval */
|
||||
}
|
||||
})();
|
||||
|
||||
/* Return result for jsbridge_eval */
|
||||
globalThis._v1984_test_result || 'RUNNING (async — check console)';
|
||||
globalThis._clavitor_test_result || 'RUNNING (async — check console)';
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
// Theme management (before anything else so it applies immediately, no flash)
|
||||
var _themes = ['', 'theme-light', 'theme-midnight'];
|
||||
var _currentTheme = localStorage.getItem('v1984_theme') || '';
|
||||
var _currentTheme = localStorage.getItem('clavitor_theme') || '';
|
||||
if (_currentTheme) document.body.className = _currentTheme;
|
||||
|
||||
function cycleTheme() {
|
||||
var idx = _themes.indexOf(_currentTheme);
|
||||
_currentTheme = _themes[(idx + 1) % _themes.length];
|
||||
document.body.className = _currentTheme;
|
||||
localStorage.setItem('v1984_theme', _currentTheme);
|
||||
localStorage.setItem('clavitor_theme', _currentTheme);
|
||||
}
|
||||
|
||||
// Stateless auth: L1 Bearer from master key in sessionStorage.
|
||||
function getL1Bearer() {
|
||||
var masterB64 = sessionStorage.getItem('v1984_master');
|
||||
var masterB64 = sessionStorage.getItem('clavitor_master');
|
||||
if (!masterB64) return null;
|
||||
var bin = atob(masterB64);
|
||||
var l1 = new Uint8Array(8);
|
||||
|
|
@ -29,7 +29,7 @@ async function api(method, path, body) {
|
|||
if (body) opts.body = JSON.stringify(body);
|
||||
var res = await fetch(path, opts);
|
||||
if (res.status === 401) {
|
||||
sessionStorage.removeItem('v1984_master');
|
||||
sessionStorage.removeItem('clavitor_master');
|
||||
location.href = '/app/';
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ var _countdownTimer = null;
|
|||
var _countdownVal = 0;
|
||||
|
||||
function _resetIdle() {
|
||||
if (!sessionStorage.getItem('v1984_master')) return;
|
||||
if (!sessionStorage.getItem('clavitor_master')) return;
|
||||
clearTimeout(_idleTimer);
|
||||
_clearCountdown();
|
||||
_idleTimer = setTimeout(_startCountdown, 60000);
|
||||
|
|
@ -60,7 +60,7 @@ function _startCountdown() {
|
|||
_countdownVal--;
|
||||
if (_countdownVal <= 0) {
|
||||
_clearCountdown();
|
||||
sessionStorage.removeItem('v1984_master');
|
||||
sessionStorage.removeItem('clavitor_master');
|
||||
window.location.href = '/app/';
|
||||
} else {
|
||||
var el = document.getElementById('lockCountdownSec');
|
||||
|
|
@ -94,11 +94,11 @@ function initTopbar() {
|
|||
});
|
||||
|
||||
nav += '<button onclick="cycleTheme()" class="topbar-lock" style="font-size:0.75rem;opacity:0.6" title="Switch theme">Theme</button>';
|
||||
nav += '<button onclick="if(typeof lockVault===\'function\'){lockVault()}else{sessionStorage.removeItem(\'v1984_master\');location.href=\'/app/\'}" class="topbar-lock">Lock</button>';
|
||||
nav += '<button onclick="if(typeof lockVault===\'function\'){lockVault()}else{sessionStorage.removeItem(\'clavitor_master\');location.href=\'/app/\'}" class="topbar-lock">Lock</button>';
|
||||
|
||||
var logo = isVault
|
||||
? '<span class="topbar-logo vaultname" onclick="if(typeof showAbout===\'function\')showAbout()" style="cursor:pointer">vault<span class="n">1984</span></span>'
|
||||
: '<a href="/app/" class="topbar-logo vaultname">vault<span class="n">1984</span></a>';
|
||||
? '<span class="topbar-logo vaultname" onclick="if(typeof showAbout===\'function\')showAbout()" style="cursor:pointer">clav<span class="n">itor</span></span>'
|
||||
: '<a href="/app/" class="topbar-logo vaultname">clav<span class="n">itor</span></a>';
|
||||
|
||||
var el = document.getElementById('topbar');
|
||||
if (el) {
|
||||
|
|
@ -107,8 +107,8 @@ function totp_remaining(period) {
|
|||
}
|
||||
|
||||
/* Export */
|
||||
if (typeof globalThis.vault1984 === 'undefined') globalThis.vault1984 = {};
|
||||
globalThis.vault1984.totp = {
|
||||
if (typeof globalThis.clavitor === 'undefined') globalThis.clavitor = {};
|
||||
globalThis.clavitor.totp = {
|
||||
generate_totp: generate_totp,
|
||||
totp_remaining: totp_remaining,
|
||||
base32_decode: base32_decode
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
var SESSION_KEY = 'v1984_master';
|
||||
var SESSION_KEY = 'clavitor_master';
|
||||
var HKDF_SALT = new TextEncoder().encode('clavitor-master-v2');
|
||||
|
||||
// Derive master key from raw PRF output and store in sessionStorage.
|
||||
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 118 B After Width: | Height: | Size: 118 B |
|
Before Width: | Height: | Size: 87 B After Width: | Height: | Size: 87 B |
|
Before Width: | Height: | Size: 100 B After Width: | Height: | Size: 100 B |
|
|
@ -1,4 +1,4 @@
|
|||
module github.com/johanj/vault1984
|
||||
module github.com/johanj/clavitor
|
||||
|
||||
go 1.24.0
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ func NormalizeKey(key []byte) []byte {
|
|||
// L1 (8 bytes) is normalized to 16 → derives 16-byte key → AES-128-GCM.
|
||||
func DeriveEntryKey(l1Key []byte, entryID int64) ([]byte, error) {
|
||||
normalized := NormalizeKey(l1Key)
|
||||
info := []byte("vault1984-entry-" + IDToHex(entryID))
|
||||
info := []byte("clavitor-entry-" + IDToHex(entryID))
|
||||
reader := hkdf.New(sha256.New, normalized, nil, info)
|
||||
key := make([]byte, len(normalized)) // 16 bytes for AES-128
|
||||
if _, err := io.ReadFull(reader, key); err != nil {
|
||||
|
|
@ -47,7 +47,7 @@ func DeriveEntryKey(l1Key []byte, entryID int64) ([]byte, error) {
|
|||
// DeriveHMACKey derives a separate HMAC key for blind indexes from L1.
|
||||
func DeriveHMACKey(l1Key []byte) ([]byte, error) {
|
||||
normalized := NormalizeKey(l1Key)
|
||||
info := []byte("vault1984-hmac-index")
|
||||
info := []byte("clavitor-hmac-index")
|
||||
reader := hkdf.New(sha256.New, normalized, nil, info)
|
||||
key := make([]byte, 32)
|
||||
if _, err := io.ReadFull(reader, key); err != nil {
|
||||
|
|
@ -20,10 +20,11 @@ import (
|
|||
// All fields zero/empty = telemetry disabled.
|
||||
type TelemetryConfig struct {
|
||||
FreqSeconds int // interval between POSTs (0 = disabled)
|
||||
Host string // e.g. https://hq.vault1984.com/telemetry
|
||||
Host string // e.g. https://hq.clavitor.com/telemetry
|
||||
Token string // Bearer token for auth
|
||||
DataDir string // vault data directory (to scan DBs)
|
||||
Mode string // "self-hosted" or "hosted"
|
||||
Version string // build version string
|
||||
}
|
||||
|
||||
// TelemetryPayload is the JSON body posted to the telemetry endpoint.
|
||||
|
|
@ -84,7 +85,7 @@ func CollectPayload(cfg TelemetryConfig, startTime time.Time) TelemetryPayload {
|
|||
hostname, _ := os.Hostname()
|
||||
|
||||
return TelemetryPayload{
|
||||
Version: "0.1.0",
|
||||
Version: cfg.Version,
|
||||
Hostname: hostname,
|
||||
UptimeSeconds: int64(time.Since(startTime).Seconds()),
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||
|
|
@ -36,13 +36,13 @@ func EnsureTLSCert(certPath, keyPath string) error {
|
|||
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{Organization: []string{"Vault1984"}, CommonName: "vault1984"},
|
||||
Subject: pkix.Name{Organization: []string{"Clavitor"}, CommonName: "clavitor"},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
|
||||
DNSNames: []string{"localhost", "vault1984.local"},
|
||||
DNSNames: []string{"localhost", "clavitor.local"},
|
||||
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
GO="${GO:-/usr/local/go/bin/go}"
|
||||
MAC_HOST="${MAC_HOST:-johanjongsma@192.168.1.14}"
|
||||
MAC_PROJECT="${MAC_PROJECT:-~/dev/clavitor/clavis/clavis-vault}"
|
||||
OUT="dist"
|
||||
|
||||
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo dev)
|
||||
COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
DATE=$(date -u +%Y%m%d-%H%M)
|
||||
LDFLAGS="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.buildDate=${DATE}"
|
||||
|
||||
mkdir -p "$OUT"
|
||||
|
||||
echo "=== Clavitor release build ==="
|
||||
echo " version: ${VERSION} (${COMMIT}) ${DATE}"
|
||||
echo ""
|
||||
|
||||
# --- Linux amd64 (native) ---
|
||||
echo "[1/4] linux/amd64 ..."
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 GOFIPS140=latest \
|
||||
$GO build -ldflags "$LDFLAGS" -o "${OUT}/clavitor-linux-amd64" ./cmd/clavitor
|
||||
echo " done"
|
||||
|
||||
# --- Linux arm64 (cross) ---
|
||||
echo "[2/4] linux/arm64 ..."
|
||||
if ! command -v aarch64-linux-gnu-gcc &>/dev/null; then
|
||||
echo " aarch64-linux-gnu-gcc not found."
|
||||
echo " Install with: sudo apt install gcc-aarch64-linux-gnu"
|
||||
echo " SKIPPED"
|
||||
else
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc GOFIPS140=latest \
|
||||
$GO build -ldflags "$LDFLAGS" -o "${OUT}/clavitor-linux-arm64" ./cmd/clavitor
|
||||
echo " done"
|
||||
fi
|
||||
|
||||
# --- Darwin arm64 + amd64 (via SSH to Mac) ---
|
||||
echo "[3/4] darwin/arm64 (via Mac SSH) ..."
|
||||
echo "[4/4] darwin/amd64 (via Mac SSH) ..."
|
||||
if ssh -o ConnectTimeout=5 -o BatchMode=yes "$MAC_HOST" true 2>/dev/null; then
|
||||
ssh "$MAC_HOST" bash -s <<REMOTE
|
||||
set -euo pipefail
|
||||
cd "${MAC_PROJECT}"
|
||||
git pull --ff-only 2>/dev/null || true
|
||||
|
||||
VERSION=\$(git describe --tags --always --dirty 2>/dev/null || echo dev)
|
||||
COMMIT=\$(git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
DATE=\$(date -u +%Y%m%d-%H%M)
|
||||
LDFLAGS="-s -w -X main.version=\${VERSION} -X main.commit=\${COMMIT} -X main.buildDate=\${DATE}"
|
||||
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 \
|
||||
go build -ldflags "\$LDFLAGS" -o /tmp/clavitor-darwin-arm64 ./cmd/clavitor
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 \
|
||||
go build -ldflags "\$LDFLAGS" -o /tmp/clavitor-darwin-amd64 ./cmd/clavitor
|
||||
REMOTE
|
||||
scp "$MAC_HOST":/tmp/clavitor-darwin-arm64 "${OUT}/clavitor-darwin-arm64"
|
||||
scp "$MAC_HOST":/tmp/clavitor-darwin-amd64 "${OUT}/clavitor-darwin-amd64"
|
||||
echo " done"
|
||||
else
|
||||
echo " Mac unreachable at ${MAC_HOST} — SKIPPED"
|
||||
fi
|
||||
|
||||
# --- Summary ---
|
||||
echo ""
|
||||
echo "=== Built artifacts ==="
|
||||
for f in "${OUT}"/clavitor-*; do
|
||||
[ -f "$f" ] || continue
|
||||
size=$(du -h "$f" | cut -f1)
|
||||
arch=$(file -b "$f" | head -c 80)
|
||||
printf " %-30s %6s %s\n" "$(basename "$f")" "$size" "$arch"
|
||||
done
|
||||
|
|
@ -10,19 +10,19 @@
|
|||
|
||||
## Deployment
|
||||
|
||||
### Dev (localhost — Florida)
|
||||
### Dev (forge = 192.168.1.16, Florida — dev.clavitor.ai)
|
||||
```
|
||||
make dev # build + restart locally
|
||||
make deploy-dev # same thing
|
||||
```
|
||||
Dev runs on forge (localhost). `dev.clavitor.ai` DNS points to home IP.
|
||||
|
||||
### Prod (Zürich — zurich.inou.com)
|
||||
### Prod (Zürich — zurich.inou.com — clavitor.ai)
|
||||
```
|
||||
make deploy-prod # cross-compile amd64, scp to Zürich, restart systemd
|
||||
```
|
||||
|
||||
Prod runs at `/opt/clavitor-web/` as systemd service `clavitor-web`.
|
||||
Caddy reverse proxies `clavitor.ai`, `clavitor.com`, `dev.clavitor.ai` → `localhost:8099`.
|
||||
Caddy reverse proxies `clavitor.ai`, `clavitor.com`, `www.clavitor.ai`, `www.clavitor.com` → `localhost:8099`.
|
||||
|
||||
### First-time setup (already done)
|
||||
```
|
||||
|
|
|
|||