chore: auto-commit uncommitted changes

This commit is contained in:
James 2026-03-19 06:02:25 -04:00
parent ec2eb2ee71
commit 2420c964b9
10 changed files with 277 additions and 76 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -34,6 +34,141 @@ typedef int socklen_t;
#include "bearssl.h"
/* --- System CA trust anchor loading --- */
/*
* Load PEM CA certificates from system trust store.
* Parses each certificate, extracts DN + public key br_x509_trust_anchor.
*/
struct pem_accum {
unsigned char *buf;
size_t len;
size_t cap;
};
static void pem_accum_push(void *dest_ctx, const void *src, size_t len) {
struct pem_accum *a = (struct pem_accum *)dest_ctx;
if (a->len + len > a->cap) {
a->cap = (a->len + len) * 2;
a->buf = realloc(a->buf, a->cap);
}
memcpy(a->buf + a->len, src, len);
a->len += len;
}
/* DN callback for x509 decoder */
struct dn_accum {
unsigned char *buf;
size_t len;
size_t cap;
};
static void dn_push(void *dest_ctx, const void *src, size_t len) {
struct dn_accum *a = (struct dn_accum *)dest_ctx;
if (a->len + len > a->cap) {
a->cap = (a->len + len) * 2;
a->buf = realloc(a->buf, a->cap);
}
memcpy(a->buf + a->len, src, len);
a->len += len;
}
static void load_system_tas(br_x509_trust_anchor **out_tas, size_t *out_count) {
*out_tas = NULL;
*out_count = 0;
static const char *ca_paths[] = {
"/etc/ssl/certs/ca-certificates.crt",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/ssl/cert.pem",
"/usr/local/share/certs/ca-root-nss.crt",
NULL
};
FILE *f = NULL;
for (int i = 0; ca_paths[i]; i++) {
f = fopen(ca_paths[i], "r");
if (f) break;
}
if (!f) return;
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
if (fsize <= 0 || fsize > 4 * 1024 * 1024) { fclose(f); return; }
char *pem = malloc((size_t)fsize);
size_t nr = fread(pem, 1, (size_t)fsize, f);
fclose(f);
/* Parse PEM → DER certificates → trust anchors */
size_t ta_cap = 256;
br_x509_trust_anchor *tas = calloc(ta_cap, sizeof(br_x509_trust_anchor));
size_t ta_count = 0;
br_pem_decoder_context pc;
br_pem_decoder_init(&pc);
struct pem_accum der = { malloc(8192), 0, 8192 };
int in_cert = 0;
size_t pos = 0;
while (pos < nr) {
size_t pushed = br_pem_decoder_push(&pc, pem + pos, nr - pos);
pos += pushed;
int event = br_pem_decoder_event(&pc);
if (event == BR_PEM_BEGIN_OBJ) {
in_cert = (strcmp(br_pem_decoder_name(&pc), "CERTIFICATE") == 0);
der.len = 0;
if (in_cert) br_pem_decoder_setdest(&pc, pem_accum_push, &der);
} else if (event == BR_PEM_END_OBJ && in_cert && der.len > 0) {
/* Decode X.509 certificate */
struct dn_accum dn = { malloc(512), 0, 512 };
br_x509_decoder_context dc;
br_x509_decoder_init(&dc, dn_push, &dn);
br_x509_decoder_push(&dc, der.buf, der.len);
if (br_x509_decoder_last_error(&dc) == 0 && br_x509_decoder_isCA(&dc)) {
br_x509_pkey *pk = br_x509_decoder_get_pkey(&dc);
if (pk && ta_count < ta_cap) {
br_x509_trust_anchor *ta = &tas[ta_count];
ta->dn.data = dn.buf;
ta->dn.len = dn.len;
ta->flags = BR_X509_TA_CA;
ta->pkey = *pk;
/* Deep copy key data (decoder buffer will be reused) */
if (pk->key_type == BR_KEYTYPE_RSA) {
unsigned char *n = malloc(pk->key.rsa.nlen);
unsigned char *e = malloc(pk->key.rsa.elen);
memcpy(n, pk->key.rsa.n, pk->key.rsa.nlen);
memcpy(e, pk->key.rsa.e, pk->key.rsa.elen);
ta->pkey.key.rsa.n = n;
ta->pkey.key.rsa.e = e;
} else if (pk->key_type == BR_KEYTYPE_EC) {
unsigned char *q = malloc(pk->key.ec.qlen);
memcpy(q, pk->key.ec.q, pk->key.ec.qlen);
ta->pkey.key.ec.q = q;
}
ta_count++;
dn.buf = NULL; /* ownership transferred */
}
}
free(dn.buf);
in_cert = 0;
} else if (event == BR_PEM_ERROR) {
in_cert = 0;
}
}
free(pem);
free(der.buf);
*out_tas = tas;
*out_count = ta_count;
}
/* --- socket connect --- */
static SOCKET tcp_connect(const char *host, const char *port) {
@ -227,19 +362,23 @@ 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,
struct v84_response *resp) {
const char *agent_name, struct v84_response *resp) {
SOCKET fd = tcp_connect(pu->host, pu->port);
if (fd == INVALID_SOCKET) return -1;
char request[2048];
char agent_hdr[256] = "";
if (agent_name && agent_name[0])
snprintf(agent_hdr, sizeof(agent_hdr), "X-Agent: %s\r\n", agent_name);
int reqlen = snprintf(request, sizeof(request),
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Authorization: Bearer %s\r\n"
"%s"
"Connection: close\r\n"
"Accept: application/json\r\n"
"\r\n",
pu->path, pu->host, bearer_token);
pu->path, pu->host, bearer_token, agent_hdr);
if (plain_send_all(fd, request, (size_t)reqlen) != 0) {
fprintf(stderr, "error: send failed\n");
@ -262,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,
struct v84_response *resp) {
const char *agent_name, struct v84_response *resp) {
SOCKET fd = tcp_connect(pu->host, pu->port);
if (fd == INVALID_SOCKET) return -1;
@ -271,14 +410,18 @@ static int http_get_tls(const struct parsed_url *pu, const char *bearer_token,
unsigned char iobuf[BR_SSL_BUFSIZE_BIDI];
/*
* TODO: load system CA bundle for production certificate validation.
* For now: no trust anchors = no validation.
* Load system CA certificates for TLS validation.
* Falls back to no-validation mode if CAs can't be loaded.
*/
br_x509_minimal_init(&xc, &br_sha256_vtable, NULL, 0);
size_t ta_count = 0;
br_x509_trust_anchor *tas = NULL;
load_system_tas(&tas, &ta_count);
br_x509_minimal_init(&xc, &br_sha256_vtable, tas, ta_count);
br_x509_minimal_set_rsa(&xc, &br_rsa_i31_pkcs1_vrfy);
br_x509_minimal_set_ecdsa(&xc, &br_ec_prime_i31, &br_ecdsa_i31_vrfy_asn1);
br_ssl_client_init_full(&sc, &xc, NULL, 0);
br_ssl_client_init_full(&sc, &xc, tas, ta_count);
br_ssl_engine_set_buffer(&sc.eng, iobuf, sizeof(iobuf), 1);
br_ssl_client_reset(&sc, pu->host, 0);
@ -286,14 +429,18 @@ static int http_get_tls(const struct parsed_url *pu, const char *bearer_token,
br_sslio_init(&ioc, &sc.eng, sock_read, &fd, sock_write, &fd);
char request[2048];
char agent_hdr[256] = "";
if (agent_name && agent_name[0])
snprintf(agent_hdr, sizeof(agent_hdr), "X-Agent: %s\r\n", agent_name);
int reqlen = snprintf(request, sizeof(request),
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Authorization: Bearer %s\r\n"
"%s"
"Connection: close\r\n"
"Accept: application/json\r\n"
"\r\n",
pu->path, pu->host, bearer_token);
pu->path, pu->host, bearer_token, agent_hdr);
if (br_sslio_write_all(&ioc, request, (size_t)reqlen) != 0) {
fprintf(stderr, "error: TLS write failed\n");
@ -328,15 +475,16 @@ 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, struct v84_response *resp) {
int http_get(const char *url, const char *bearer_token, const char *agent_name,
struct v84_response *resp) {
memset(resp, 0, sizeof(*resp));
struct parsed_url pu;
if (parse_url(url, &pu) != 0) return -1;
if (pu.use_tls) {
return http_get_tls(&pu, bearer_token, resp);
return http_get_tls(&pu, bearer_token, agent_name, resp);
} else {
return http_get_plain(&pu, bearer_token, resp);
return http_get_plain(&pu, bearer_token, agent_name, resp);
}
}

View File

@ -13,7 +13,9 @@ struct v84_response {
size_t body_len;
};
/* Perform HTTPS GET with Bearer auth. Returns 0 on success, -1 on error. */
int http_get(const char *url, const char *bearer_token, struct v84_response *resp);
/* 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);
#endif

View File

@ -503,43 +503,37 @@ char *jsbridge_eval(const char *code) {
return out;
}
char *jsbridge_l2_encrypt(const unsigned char *l2_key, size_t key_len,
const char *entry_id, const char *field_label,
const char *plaintext) {
char *jsbridge_encrypt_field(const unsigned char *key, size_t key_len,
const char *field_label, const char *plaintext) {
if (!ctx) return NULL;
/* Call vault1984.crypto.l2_encrypt_field(key, entry_id, label, plaintext) */
JSValue global = JS_GetGlobalObject(ctx);
JSValue v84 = JS_GetPropertyStr(ctx, global, "vault1984");
JSValue crypto_obj = JS_GetPropertyStr(ctx, v84, "crypto");
JSValue fn = JS_GetPropertyStr(ctx, crypto_obj, "l2_encrypt_field");
JSValue fn = JS_GetPropertyStr(ctx, crypto_obj, "encrypt_field");
JSValue key_arr = js_new_uint8array(ctx, l2_key, key_len);
JSValue args[4] = {
JSValue key_arr = js_new_uint8array(ctx, key, key_len);
JSValue args[3] = {
key_arr,
JS_NewString(ctx, entry_id),
JS_NewString(ctx, field_label),
JS_NewString(ctx, plaintext)
};
JSValue result = JS_Call(ctx, fn, JS_UNDEFINED, 4, args);
JSValue result = JS_Call(ctx, fn, JS_UNDEFINED, 3, args);
char *out = NULL;
if (!JS_IsException(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) {
out = strdup(str);
JS_FreeCString(ctx, str);
}
if (str) { out = strdup(str); JS_FreeCString(ctx, str); }
} else {
JSValue exc = JS_GetException(ctx);
const char *msg = JS_ToCString(ctx, exc);
fprintf(stderr, "error: l2_encrypt: %s\n", msg ? msg : "unknown");
fprintf(stderr, "error: encrypt_field: %s\n", msg ? msg : "unknown");
if (msg) JS_FreeCString(ctx, msg);
JS_FreeValue(ctx, exc);
}
for (int i = 0; i < 4; i++) JS_FreeValue(ctx, args[i]);
for (int i = 0; i < 3; i++) JS_FreeValue(ctx, args[i]);
JS_FreeValue(ctx, result);
JS_FreeValue(ctx, fn);
JS_FreeValue(ctx, crypto_obj);
@ -548,42 +542,37 @@ char *jsbridge_l2_encrypt(const unsigned char *l2_key, size_t key_len,
return out;
}
char *jsbridge_l2_decrypt(const unsigned char *l2_key, size_t key_len,
const char *entry_id, const char *field_label,
const char *ciphertext_b64) {
char *jsbridge_decrypt_field(const unsigned char *key, size_t key_len,
const char *field_label, const char *ciphertext_b64) {
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 fn = JS_GetPropertyStr(ctx, crypto_obj, "l2_decrypt_field");
JSValue fn = JS_GetPropertyStr(ctx, crypto_obj, "decrypt_field");
JSValue key_arr = js_new_uint8array(ctx, l2_key, key_len);
JSValue args[4] = {
JSValue key_arr = js_new_uint8array(ctx, key, key_len);
JSValue args[3] = {
key_arr,
JS_NewString(ctx, entry_id),
JS_NewString(ctx, field_label),
JS_NewString(ctx, ciphertext_b64)
};
JSValue result = JS_Call(ctx, fn, JS_UNDEFINED, 4, args);
JSValue result = JS_Call(ctx, fn, JS_UNDEFINED, 3, args);
char *out = NULL;
if (!JS_IsException(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) {
out = strdup(str);
JS_FreeCString(ctx, str);
}
if (str) { out = strdup(str); JS_FreeCString(ctx, str); }
} else {
JSValue exc = JS_GetException(ctx);
const char *msg = JS_ToCString(ctx, exc);
fprintf(stderr, "error: l2_decrypt: %s\n", msg ? msg : "unknown");
fprintf(stderr, "error: decrypt_field: %s\n", msg ? msg : "unknown");
if (msg) JS_FreeCString(ctx, msg);
JS_FreeValue(ctx, exc);
}
for (int i = 0; i < 4; i++) JS_FreeValue(ctx, args[i]);
for (int i = 0; i < 3; i++) JS_FreeValue(ctx, args[i]);
JS_FreeValue(ctx, result);
JS_FreeValue(ctx, fn);
JS_FreeValue(ctx, crypto_obj);

View File

@ -15,22 +15,20 @@ int jsbridge_init(void);
void jsbridge_cleanup(void);
/*
* Encrypt a field value using L2 key.
* Returns base64-encoded ciphertext (caller frees).
* Returns NULL on error.
* Encrypt a field value. Key length determines tier (8=L1, 16=L2, 32=L3).
* Uses crypto.js encrypt_field() the single source of truth.
* Returns base64-encoded ciphertext (caller frees). NULL on error.
*/
char *jsbridge_l2_encrypt(const unsigned char *l2_key, size_t key_len,
const char *entry_id, const char *field_label,
const char *plaintext);
char *jsbridge_encrypt_field(const unsigned char *key, size_t key_len,
const char *field_label, const char *plaintext);
/*
* Decrypt a field value using L2 key.
* Returns plaintext string (caller frees).
* Returns NULL on error.
* Decrypt a field value. Key length determines tier (8=L1, 16=L2, 32=L3).
* Uses crypto.js decrypt_field() the single source of truth.
* Returns plaintext string (caller frees). NULL on error.
*/
char *jsbridge_l2_decrypt(const unsigned char *l2_key, size_t key_len,
const char *entry_id, const char *field_label,
const char *ciphertext_b64);
char *jsbridge_decrypt_field(const unsigned char *key, size_t key_len,
const char *field_label, const char *ciphertext_b64);
/*
* Generate TOTP code from a base32-encoded seed.

View File

@ -43,7 +43,7 @@ static void build_url(char *buf, size_t len, const struct v84_config *cfg, const
}
static void get_bearer(const struct v84_config *cfg, char *buf, size_t len) {
base64_encode(cfg->l2_key, 16, buf, len);
base64_encode(cfg->l2_key, 8, buf, len); /* L1 = first 8 bytes */
}
/* --- test commands --- */
@ -126,11 +126,11 @@ static int cmd_test_crypto(void) {
/* L2 encrypt/decrypt via C API */
fprintf(stderr, " encrypting: \"s3cret-p@ssw0rd!\"\n");
char *ct = jsbridge_l2_encrypt(test_key, 16, "abcdef0123456789", "password", "s3cret-p@ssw0rd!");
char *ct = jsbridge_encrypt_field(test_key, 16, "password", "s3cret-p@ssw0rd!");
if (!ct) { fprintf(stderr, "FAIL: l2_encrypt\n"); jsbridge_cleanup(); return 1; }
fprintf(stderr, " ciphertext: %s\n", ct);
char *pt = jsbridge_l2_decrypt(test_key, 16, "abcdef0123456789", "password", ct);
char *pt = jsbridge_decrypt_field(test_key, 16, "password", ct);
free(ct);
if (!pt || strcmp(pt, "s3cret-p@ssw0rd!") != 0) {
fprintf(stderr, "FAIL: l2_decrypt\n"); free(pt); jsbridge_cleanup(); return 1;
@ -139,10 +139,10 @@ static int cmd_test_crypto(void) {
free(pt);
/* Wrong key rejection */
char *ct2 = jsbridge_l2_encrypt(test_key, 16, "abcdef0123456789", "pw", "test");
char *ct2 = jsbridge_encrypt_field(test_key, 16, "pw", "test");
if (ct2) {
unsigned char wrong[16] = {0};
char *bad = jsbridge_l2_decrypt(wrong, 16, "abcdef0123456789", "pw", ct2);
char *bad = jsbridge_decrypt_field(wrong, 16, "pw", ct2);
if (bad) { fprintf(stderr, "FAIL: wrong key accepted\n"); free(bad); free(ct2); jsbridge_cleanup(); return 1; }
fprintf(stderr, " wrong key rejection: OK\n");
free(ct2);
@ -177,6 +177,40 @@ int main(int argc, char **argv) {
if (strcmp(cmd, "--help") == 0 || strcmp(cmd, "-h") == 0) { usage(); return 0; }
if (strcmp(cmd, "--version") == 0) { printf("vault1984-cli %s\n", VERSION); return 0; }
if (strcmp(cmd, "test-crypto") == 0) { return cmd_test_crypto(); }
if (strcmp(cmd, "test-roundtrip") == 0) {
if (jsbridge_init() != 0) { fprintf(stderr, "error: crypto init failed\n"); return 1; }
char *r = jsbridge_eval(
"var R = [];"
"function t(kb, label, pt) {"
" var k = new Uint8Array(kb);"
" var d = kb.length + 'B key, label=' + label;"
" try {"
" var ct = vault1984.crypto.encrypt_field(k, label, pt);"
" var p2 = vault1984.crypto.decrypt_field(k, label, ct);"
" R.push(d + ': ' + (p2 === pt ? 'PASS' : 'FAIL got=' + p2));"
" } catch(e) { R.push(d + ': ERROR ' + e.message); }"
"}"
/* 8-byte key */
"t([1,2,3,4,5,6,7,8], 'username', 'johanj');"
/* 16-byte key (L2) */
"t([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], 'password', 's3cret!');"
"t([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], 'totp', 'JBSWY3DPEHPK3PXP');"
"t([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], 'Number', '5452120017212208');"
/* 32-byte key (L3) */
"t([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32], 'passport', 'NL12345678');"
"t([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32], 'CVV', '755');"
/* wrong key must fail */
"var k1=new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);"
"var k2=new Uint8Array([99,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);"
"try{var c=vault1984.crypto.encrypt_field(k1,'x','s');vault1984.crypto.decrypt_field(k2,'x',c);R.push('wrong-key: FAIL')}catch(e){R.push('wrong-key rejection: PASS')}"
/* wrong label must fail */
"try{var c2=vault1984.crypto.encrypt_field(k1,'a','s');vault1984.crypto.decrypt_field(k1,'b',c2);R.push('wrong-label: FAIL')}catch(e){R.push('wrong-label rejection: PASS')}"
"R.join('\\n');"
);
if (r) { printf("%s\n", r); free(r); }
jsbridge_cleanup();
return (r && strstr(r, "FAIL") == NULL && strstr(r, "ERROR") == NULL) ? 0 : 1;
}
if (strcmp(cmd, "test-totp") == 0) {
if (argc < 3) {
fprintf(stderr, "usage: vault1984-cli test-totp <base32-seed>\n");
@ -220,9 +254,8 @@ int main(int argc, char **argv) {
char js_code[2048];
snprintf(js_code, sizeof(js_code),
"(function() {"
" var enc = new TextEncoder();"
" var seed = enc.encode('vault1984-l2-');"
" var encKey = native_hkdf_sha256(seed, null, enc.encode('token'), 16);"
" var seed = native_encode_utf8('vault1984-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);"
" return native_base64_encode(pt);"
@ -295,7 +328,7 @@ int main(int argc, char **argv) {
}
struct v84_response resp;
if (http_get(url, bearer, &resp) != 0) { fprintf(stderr, "error: request failed\n"); return 1; }
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; }
@ -329,7 +362,7 @@ int main(int argc, char **argv) {
build_url(url, sizeof(url), &cfg, path);
struct v84_response resp;
if (http_get(url, bearer, &resp) != 0) { fprintf(stderr, "error: search failed\n"); return 1; }
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; }
cJSON *results = cJSON_Parse(resp.body); free(resp.body);
@ -345,7 +378,7 @@ int main(int argc, char **argv) {
snprintf(path, sizeof(path), "/api/ext/totp/%s", entry_id);
build_url(url, sizeof(url), &cfg, path);
cJSON_Delete(results);
if (http_get(url, bearer, &resp) != 0) { fprintf(stderr, "error: TOTP request failed\n"); return 1; }
if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: TOTP request failed\n"); return 1; }
if (resp.status != 200) { fprintf(stderr, "error: server returned %d\n", resp.status); free(resp.body); return 1; }
cJSON *totp = cJSON_Parse(resp.body); free(resp.body);
if (!totp) { fprintf(stderr, "error: invalid JSON\n"); return 1; }
@ -357,7 +390,7 @@ int main(int argc, char **argv) {
snprintf(path, sizeof(path), "/api/entries/%s", entry_id);
build_url(url, sizeof(url), &cfg, path);
cJSON_Delete(results);
if (http_get(url, bearer, &resp) != 0) { fprintf(stderr, "error: fetch failed\n"); return 1; }
if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: fetch 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; }
@ -371,11 +404,25 @@ int main(int argc, char **argv) {
cJSON_ArrayForEach(field, fields) {
const char *label = cJSON_GetStringValue(cJSON_GetObjectItem(field, "label"));
const char *value = cJSON_GetStringValue(cJSON_GetObjectItem(field, "value"));
cJSON *tier = cJSON_GetObjectItem(field, "tier");
int tier_val = tier ? tier->valueint : 1;
cJSON *tier_j = cJSON_GetObjectItem(field, "tier");
cJSON *l2_j = cJSON_GetObjectItem(field, "l2");
int tier_val = tier_j ? tier_j->valueint : (l2_j && cJSON_IsTrue(l2_j) ? 2 : 1);
if (!label) continue;
if (tier_val >= 3) printf("%s: [L3 -- requires hardware key]\n", label);
else printf("%s: %s\n", label, value ? value : "");
if (tier_val >= 3) {
printf("%s: [L3 -- requires hardware key]\n", label);
} else if (tier_val == 2 && value && value[0]) {
/* Decrypt L2 field locally */
char *pt = jsbridge_decrypt_field(cfg.l2_key, 16, label, value);
if (pt) {
printf("%s: %s\n", label, pt);
free(pt);
} else {
printf("%s: [L2 -- decryption failed]\n", label);
}
} else {
printf("%s: %s\n", label, value ? value : "");
}
}
}
cJSON_Delete(entry);

Binary file not shown.

View File

@ -131,21 +131,37 @@ function hkdf_sha256(ikm, salt, info, length) {
* @param {string} plaintext - field value to encrypt
* @returns {string|Promise<string>} base64-encoded ciphertext
*/
/*
* Normalize key for AES: 8-byte keys are doubled to 16 bytes.
* AES requires 16, 24, or 32 byte keys.
* HKDF output length matches the (normalized) key length.
*/
function normalize_key(key) {
if (key.length === 8) {
var doubled = new Uint8Array(16);
doubled.set(key, 0);
doubled.set(key, 8);
return doubled;
}
return key;
}
function encrypt_field(key, field_label, plaintext) {
var info_str = 'vault1984-field-' + field_label;
var key_len = key.length;
var nkey = normalize_key(key);
var aes_len = nkey.length; /* 16 or 32 */
if (IS_BROWSER) {
var enc = new TextEncoder();
var info = enc.encode(info_str);
return hkdf_sha256(key, null, info, key_len).then(function(field_key) {
return hkdf_sha256(nkey, null, info, aes_len).then(function(field_key) {
return aes_gcm_encrypt(field_key, enc.encode(plaintext));
}).then(function(ct) {
return uint8_to_base64(ct);
});
} else {
var info = native_encode_utf8(info_str);
var field_key = native_hkdf_sha256(key, null, info, key_len);
var field_key = native_hkdf_sha256(nkey, null, info, aes_len);
var pt_bytes = native_encode_utf8(plaintext);
var ct = native_aes_gcm_encrypt(field_key, pt_bytes);
return native_base64_encode(ct);
@ -162,13 +178,14 @@ function encrypt_field(key, field_label, plaintext) {
*/
function decrypt_field(key, field_label, ciphertext_b64) {
var info_str = 'vault1984-field-' + field_label;
var key_len = key.length;
var nkey = normalize_key(key);
var aes_len = nkey.length;
if (IS_BROWSER) {
var enc = new TextEncoder();
var dec = new TextDecoder();
var info = enc.encode(info_str);
return hkdf_sha256(key, null, info, key_len).then(function(field_key) {
return hkdf_sha256(nkey, null, info, aes_len).then(function(field_key) {
var ct = base64_to_uint8(ciphertext_b64);
return aes_gcm_decrypt(field_key, ct);
}).then(function(pt) {
@ -176,7 +193,7 @@ function decrypt_field(key, field_label, ciphertext_b64) {
});
} else {
var info = native_encode_utf8(info_str);
var field_key = native_hkdf_sha256(key, null, info, key_len);
var field_key = native_hkdf_sha256(nkey, null, info, aes_len);
var ct = native_base64_decode(ciphertext_b64);
var pt = native_aes_gcm_decrypt_blob(field_key, ct);
return native_decode_utf8(pt);