chore: auto-commit uncommitted changes
This commit is contained in:
parent
2420c964b9
commit
2ae57f82b6
Binary file not shown.
Binary file not shown.
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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 |
Loading…
Reference in New Issue