chore: auto-commit uncommitted changes

This commit is contained in:
James 2026-03-19 12:03:27 -04:00
parent 2420c964b9
commit 2ae57f82b6
11 changed files with 281 additions and 30 deletions

Binary file not shown.

Binary file not shown.

View File

@ -484,6 +484,11 @@ char *jsbridge_totp(const char *seed_b32) {
return out;
}
int jsbridge_load(const char *filename) {
if (!ctx) return -1;
return load_js_file(ctx, filename);
}
char *jsbridge_eval(const char *code) {
if (!ctx) return NULL;
JSValue val = JS_Eval(ctx, code, strlen(code), "<eval>", JS_EVAL_TYPE_GLOBAL);

View File

@ -39,4 +39,7 @@ char *jsbridge_totp(const char *seed_b32);
/* Evaluate JS code and return result as string (caller frees). NULL on error. */
char *jsbridge_eval(const char *code);
/* Load and evaluate a JS file. Returns 0 on success. */
int jsbridge_load(const char *filename);
#endif

View File

@ -179,37 +179,24 @@ int main(int argc, char **argv) {
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 (jsbridge_load("crypto/test_crypto.js") != 0) {
fprintf(stderr, "error: cannot load test_crypto.js\n");
jsbridge_cleanup();
return 1;
}
char *r = jsbridge_eval("globalThis._v1984_test_result");
if (r) { printf("%s\n", r); }
int ok = (r && strstr(r, "FAILED") == NULL);
free(r);
jsbridge_cleanup();
return ok ? 0 : 1;
}
if (strcmp(cmd, "eval") == 0 && argc > 2) {
if (jsbridge_init() != 0) { fprintf(stderr, "error: crypto init failed\n"); return 1; }
char *r = jsbridge_eval(argv[2]);
if (r) { printf("%s\n", r); free(r); }
jsbridge_cleanup();
return (r && strstr(r, "FAIL") == NULL && strstr(r, "ERROR") == NULL) ? 0 : 1;
return r ? 0 : 1;
}
if (strcmp(cmd, "test-totp") == 0) {
if (argc < 3) {
@ -307,6 +294,11 @@ int main(int argc, char **argv) {
snprintf(cfg.agent_name, sizeof(cfg.agent_name), "%s", agent_name);
memcpy(cfg.l2_key, l2_key, 16);
/* Debug: show L2 key for verification — REMOVE BEFORE RELEASE */
fprintf(stderr, "L2 key: [");
for (int i = 0; i < 16; i++) fprintf(stderr, "%s%d", i?",":"", cfg.l2_key[i]);
fprintf(stderr, "]\n");
/* L1 = first 8 bytes of L2 key, used as Bearer auth */
char bearer[32];
get_bearer(&cfg, bearer, sizeof(bearer));
@ -406,7 +398,7 @@ int main(int argc, char **argv) {
const char *value = cJSON_GetStringValue(cJSON_GetObjectItem(field, "value"));
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);
int tier_val = tier_j ? tier_j->valueint : (l2_j && cJSON_IsTrue(l2_j) ? 3 : 1);
if (!label) continue;
if (tier_val >= 3) {

Binary file not shown.

251
crypto/test_crypto.js Normal file
View File

@ -0,0 +1,251 @@
/*
* vault1984 crypto test suite
* Runs in both QuickJS (CLI) and browser.
*
* CLI: vault1984-cli test-roundtrip
* Web: open browser console, paste: fetch('/app/test_crypto.js').then(r=>r.text()).then(eval)
* or load as <script> in a test page
*
* All tests must produce identical results on both platforms.
* If any test fails on one but passes on the other, the shared crypto is broken.
*/
(function() {
var R = []; /* results */
var FAIL = false;
function pass(name) { R.push('PASS ' + name); }
function fail(name, detail) { R.push('FAIL ' + name + (detail ? ' — ' + detail : '')); FAIL = true; }
/* Wrap a test that might be sync (QuickJS) or async (browser) */
function resolve(val, cb) {
if (val && typeof val.then === 'function') {
val.then(function(r) { cb(r); }).catch(function(e) { cb(null, e); });
} else {
cb(val);
}
}
/* Safe call: catches sync throws (QuickJS) and async rejections (browser) */
function safe(fn, cb) {
try {
var result = fn();
if (result && typeof result.then === 'function') {
result.then(function(r) { cb(r); }).catch(function(e) { cb(null, e); });
} else {
cb(result);
}
} catch(e) {
cb(null, e);
}
}
/* --- Key fixtures --- */
var K8 = new Uint8Array([11,22,33,44,55,66,77,88]);
var K16 = new Uint8Array([11,22,33,44,55,66,77,88,99,110,111,112,113,114,115,116]);
var K32 = new Uint8Array([11,22,33,44,55,66,77,88,99,110,111,112,113,114,115,116,
201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216]);
/* K16 is the first 16 bytes of K32 — by design (truncation model) */
/* K8 is the first 8 bytes of K16 — by design */
var WRONG_K16 = new Uint8Array([255,22,33,44,55,66,77,88,99,110,111,112,113,114,115,116]);
var tests = [];
/* --- 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) {
if (err) { fail(name, err.message); done(); return; }
resolve(vault1984.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 + '"');
done();
});
});
});
/* --- 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) {
if (err) { fail(name, err.message); done(); return; }
resolve(vault1984.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 + '"');
done();
});
});
});
/* --- 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) {
if (err) { fail(name, err.message); done(); return; }
resolve(vault1984.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 + '"');
done();
});
});
});
/* --- 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) {
if (err) { fail(name, 'encrypt failed: ' + err.message); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K16, 'passport', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'L2 key decrypted L3 data to "' + pt + '"');
done();
});
});
});
/* --- 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) {
if (err) { fail(name, 'encrypt failed: ' + err.message); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K32, 'password', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'L3 key decrypted L2 data to "' + pt + '"');
done();
});
});
});
/* --- 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) {
if (err) { fail(name, 'encrypt failed'); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(WRONG_K16, 'secret', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'wrong key decrypted to "' + pt + '"');
done();
});
});
});
/* --- 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) {
if (err) { fail(name, 'encrypt failed'); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K16, 'labelB', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'wrong label decrypted to "' + pt + '"');
done();
});
});
});
/* --- Test 8: Empty string roundtrip --- */
tests.push(function(done) {
var name = 'empty string roundtrip';
resolve(vault1984.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) {
if (err2) fail(name, err2.message);
else if (pt === '') pass(name);
else fail(name, 'got "' + pt + '"');
done();
});
});
});
/* --- Test 9: Unicode roundtrip --- */
tests.push(function(done) {
var name = 'unicode roundtrip';
var unicode = 'pässwörd 密码 🔑';
resolve(vault1984.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) {
if (err2) fail(name, err2.message);
else if (pt === unicode) pass(name);
else fail(name, 'mismatch');
done();
});
});
});
/* --- Test 10: L1 key (8B) produces different ciphertext than L2 key (16B) --- */
tests.push(function(done) {
var name = 'L1 and L2 keys produce different ciphertexts';
/* K8 doubled = [11,22,33,44,55,66,77,88,11,22,33,44,55,66,77,88]
* 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) {
if (err) { fail(name, 'L1 encrypt: ' + err.message); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K16, 'field', ct1); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'L2 key decrypted L1 data');
done();
});
});
});
/* --- 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);
resolve(result, function(code, err) {
if (err) fail(name, err.message);
else if (code === '287082') pass(name);
else fail(name, 'got ' + code + ', expected 287082');
done();
});
});
/* --- 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);
resolve(result, function(code, err) {
if (err) fail(name, err.message);
else if (code === '07081804') pass(name);
else fail(name, 'got ' + code + ', expected 07081804');
done();
});
});
/* --- Runner --- */
function run(idx) {
if (idx >= tests.length) {
/* Summary */
var summary = '\n' + R.join('\n') + '\n\n' +
(FAIL ? 'FAILED' : 'ALL ' + tests.length + ' TESTS PASSED');
if (typeof globalThis.document !== 'undefined') {
/* Browser: log to console */
console.log(summary);
R.forEach(function(line) {
if (line.indexOf('FAIL') === 0) console.error(line);
else console.log(line);
});
}
/* Return result string (for QuickJS eval or browser display) */
globalThis._v1984_test_result = summary;
return;
}
tests[idx](function() { run(idx + 1); });
}
run(0);
/* For sync environments (QuickJS), result is available immediately */
if (typeof globalThis._v1984_test_result !== 'undefined') {
/* Used by CLI eval */
}
})();
/* Return result for jsbridge_eval */
globalThis._v1984_test_result || 'RUNNING (async — check console)';

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 KiB

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 1006 KiB