feat(crypto): Argon2id KDF foundation (vendored, not yet adopted)
Phase 1 of CODE_AUDIT §1.2 — additive, no live account uses Argon2id yet. - Vendor @noble/hashes@2.2.0 argon2id as js/argon2.js (esbuild IIFE exposing globalThis.NobleArgon2). Pure-JS, not WASM: CSP is script-src 'self' with no wasm-unsafe-eval, so WASM would require weakening it. Verified against the RFC 9106 §5.3 test vector. Server needs zero Argon2 (zero-knowledge: it only ever SHA256-wraps the client verifier). - app.js: deriveKeyBytes(pwd, salt, algo, iters, argonParams) branches Argon2id vs PBKDF2; deriveKeyAndVerifier refactored around it. New markers HASH_ALGO_ARGON2='argon2id-v2' + ARGON2_DEFAULT_PARAMS (OWASP m=19MiB,t=2,p=1, ~0.65s/unlock). isDecoupledVerifierAlgo() generalises the decoupled-verifier rule to any '-v2' scheme so argon2id-v2 inherits it. AES key is still ALWAYS the raw KDF output → entries decryptable, legacy accounts untouched. - index.html loads js/argon2.js before app.js; added to BuildAssets whitelist; test harness loads it into the sandbox first. - Tests: +5 (RFC 9106 vector via vendored bundle, argon2 branch derives Argon2 key not PBKDF2, decoupled verifier, AES round-trip under Argon2 key). 40/40. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -714,46 +714,75 @@ function bytesToHex(arr) {
|
||||
return hex;
|
||||
}
|
||||
|
||||
// Decoupled-verifier scheme marker + domain separator. When the account's
|
||||
// hash_algo is HASH_ALGO_V2, the verifier sent to the server is a one-way
|
||||
// Decoupled-verifier scheme markers + domain separator. When the account's
|
||||
// hash_algo ends in '-v2', the verifier sent to the server is a one-way
|
||||
// SHA-256 of the key hex (domain-separated), NOT the key hex itself — so
|
||||
// intercepting the /login body no longer hands over the AES vault key.
|
||||
// The AES key (cryptoKey) is ALWAYS the raw PBKDF2 output regardless, so
|
||||
// The AES key (cryptoKey) is ALWAYS the raw KDF output regardless of algo, so
|
||||
// entries stay decryptable and legacy accounts are unaffected.
|
||||
const HASH_ALGO_V2 = 'pbkdf2-sha256-v2';
|
||||
const HASH_ALGO_V2 = 'pbkdf2-sha256-v2'; // PBKDF2 KDF + decoupled verifier
|
||||
const HASH_ALGO_ARGON2 = 'argon2id-v2'; // Argon2id KDF + decoupled verifier
|
||||
const AUTH_VERIFIER_DOMAIN = 'pmserver/auth-verifier/v2';
|
||||
|
||||
// OWASP-recommended Argon2id baseline (m = 19 MiB, t = 2, p = 1). Stored
|
||||
// per-account (like kdfIterations for PBKDF2) so it's tunable later without
|
||||
// breaking existing accounts. dkLen is fixed at 32 (AES-256 key).
|
||||
const ARGON2_DEFAULT_PARAMS = { m: 19456, t: 2, p: 1 };
|
||||
|
||||
// True for any scheme whose transmitted verifier is decoupled from the key
|
||||
// (all '-v2' markers: pbkdf2-sha256-v2, argon2id-v2). endsWith keeps it
|
||||
// future-proof for any later '-v2' KDF.
|
||||
function isDecoupledVerifierAlgo(algo) {
|
||||
return typeof algo === 'string' && algo.endsWith('-v2');
|
||||
}
|
||||
|
||||
async function sha256Hex(str) {
|
||||
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
|
||||
return bytesToHex(new Uint8Array(buf));
|
||||
}
|
||||
|
||||
// Map the raw PBKDF2 key hex → the verifier to transmit, per account algo.
|
||||
// v2 → domain-separated SHA-256 (decoupled from the key). Anything else →
|
||||
// the key hex verbatim (legacy behaviour, unchanged for existing accounts).
|
||||
// Map the raw KDF key hex → the verifier to transmit, per account algo.
|
||||
// Decoupled ('-v2') → domain-separated SHA-256. Anything else → the key hex
|
||||
// verbatim (legacy behaviour, unchanged for existing pre-v2 accounts).
|
||||
async function verifierFromKeyHex(keyHex, algo) {
|
||||
if (algo === HASH_ALGO_V2) return await sha256Hex(keyHex + AUTH_VERIFIER_DOMAIN);
|
||||
if (isDecoupledVerifierAlgo(algo)) return await sha256Hex(keyHex + AUTH_VERIFIER_DOMAIN);
|
||||
return keyHex;
|
||||
}
|
||||
|
||||
async function deriveKeyAndVerifier(pwd, saltHex, iterations, algo) {
|
||||
iterations = iterations || 100000;
|
||||
// Derive the 32 raw key bytes from the master password, per account KDF.
|
||||
// Argon2id (memory-hard) for argon2id-* accounts, else PBKDF2-SHA256. Both
|
||||
// feed the salt HEX STRING's UTF-8 bytes as the salt (historical quirk kept
|
||||
// identical across KDFs so a given pw+salt maps to one deterministic key).
|
||||
async function deriveKeyBytes(pwd, saltHex, algo, iterations, argonParams) {
|
||||
const enc = new TextEncoder();
|
||||
if (algo === HASH_ALGO_ARGON2) {
|
||||
if (typeof NobleArgon2 === 'undefined' || !NobleArgon2 || !NobleArgon2.argon2id)
|
||||
throw new Error('Argon2 library not loaded (js/argon2.js missing?)');
|
||||
const p = argonParams || ARGON2_DEFAULT_PARAMS;
|
||||
return NobleArgon2.argon2id(enc.encode(pwd), enc.encode(saltHex),
|
||||
{ t: p.t, m: p.m, p: p.p, dkLen: 32, version: 0x13 });
|
||||
}
|
||||
// PBKDF2-SHA256 (default / legacy).
|
||||
iterations = iterations || 100000;
|
||||
const km = await crypto.subtle.importKey(
|
||||
'raw', enc.encode(pwd), 'PBKDF2', false, ['deriveBits']);
|
||||
const bits = await crypto.subtle.deriveBits(
|
||||
{ name: 'PBKDF2', salt: enc.encode(saltHex),
|
||||
iterations: iterations, hash: 'SHA-256' },
|
||||
km, 256); // 256 bits = 32 bytes — matches PBKDF2_SHA256_Hex output
|
||||
const keyBytes = new Uint8Array(bits);
|
||||
return new Uint8Array(bits);
|
||||
}
|
||||
|
||||
async function deriveKeyAndVerifier(pwd, saltHex, iterations, algo, argonParams) {
|
||||
const keyBytes = await deriveKeyBytes(pwd, saltHex, algo, iterations, argonParams);
|
||||
const cryptoKey = await crypto.subtle.importKey(
|
||||
'raw', keyBytes, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);
|
||||
const verifier = await verifierFromKeyHex(bytesToHex(keyBytes), algo);
|
||||
return { cryptoKey, verifier };
|
||||
}
|
||||
|
||||
async function computeVerifier(pwd, saltHex, iterations, algo) {
|
||||
const r = await deriveKeyAndVerifier(pwd, saltHex, iterations, algo);
|
||||
async function computeVerifier(pwd, saltHex, iterations, algo, argonParams) {
|
||||
const r = await deriveKeyAndVerifier(pwd, saltHex, iterations, algo, argonParams);
|
||||
return r.verifier;
|
||||
}
|
||||
|
||||
|
||||
+957
@@ -0,0 +1,957 @@
|
||||
/* @noble/hashes argon2id v2.2.0 — vendored bundle (esbuild IIFE). Verified against RFC 9106 §5.3. Do not edit by hand; re-bundle from @noble/hashes@2.2.0. */
|
||||
(() => {
|
||||
var __defProp = Object.defineProperty;
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
|
||||
// node_modules/@noble/hashes/_u64.js
|
||||
var U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
||||
var _32n = /* @__PURE__ */ BigInt(32);
|
||||
function fromBig(n, le = false) {
|
||||
if (le)
|
||||
return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
|
||||
return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
|
||||
}
|
||||
var rotrSH = (h, l, s) => h >>> s | l << 32 - s;
|
||||
var rotrSL = (h, l, s) => h << 32 - s | l >>> s;
|
||||
var rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32;
|
||||
var rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s;
|
||||
var rotr32H = (_h, l) => l;
|
||||
var rotr32L = (h, _l) => h;
|
||||
function add(Ah, Al, Bh, Bl) {
|
||||
const l = (Al >>> 0) + (Bl >>> 0);
|
||||
return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 };
|
||||
}
|
||||
var add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
|
||||
var add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
|
||||
|
||||
// node_modules/@noble/hashes/utils.js
|
||||
function isBytes(a) {
|
||||
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
||||
}
|
||||
function anumber(n, title = "") {
|
||||
if (typeof n !== "number") {
|
||||
const prefix = title && `"${title}" `;
|
||||
throw new TypeError(`${prefix}expected number, got ${typeof n}`);
|
||||
}
|
||||
if (!Number.isSafeInteger(n) || n < 0) {
|
||||
const prefix = title && `"${title}" `;
|
||||
throw new RangeError(`${prefix}expected integer >= 0, got ${n}`);
|
||||
}
|
||||
}
|
||||
function abytes(value, length, title = "") {
|
||||
const bytes = isBytes(value);
|
||||
const len = value?.length;
|
||||
const needsLen = length !== void 0;
|
||||
if (!bytes || needsLen && len !== length) {
|
||||
const prefix = title && `"${title}" `;
|
||||
const ofLen = needsLen ? ` of length ${length}` : "";
|
||||
const got = bytes ? `length=${len}` : `type=${typeof value}`;
|
||||
const message = prefix + "expected Uint8Array" + ofLen + ", got " + got;
|
||||
if (!bytes)
|
||||
throw new TypeError(message);
|
||||
throw new RangeError(message);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function aexists(instance, checkFinished = true) {
|
||||
if (instance.destroyed)
|
||||
throw new Error("Hash instance has been destroyed");
|
||||
if (checkFinished && instance.finished)
|
||||
throw new Error("Hash#digest() has already been called");
|
||||
}
|
||||
function aoutput(out, instance) {
|
||||
abytes(out, void 0, "digestInto() output");
|
||||
const min = instance.outputLen;
|
||||
if (out.length < min) {
|
||||
throw new RangeError('"digestInto() output" expected to be of length >=' + min);
|
||||
}
|
||||
}
|
||||
function u8(arr) {
|
||||
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||
}
|
||||
function u32(arr) {
|
||||
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||
}
|
||||
function clean(...arrays) {
|
||||
for (let i = 0; i < arrays.length; i++) {
|
||||
arrays[i].fill(0);
|
||||
}
|
||||
}
|
||||
var isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
|
||||
function byteSwap(word) {
|
||||
return word << 24 & 4278190080 | word << 8 & 16711680 | word >>> 8 & 65280 | word >>> 24 & 255;
|
||||
}
|
||||
var swap8IfBE = isLE ? (n) => n : (n) => byteSwap(n) >>> 0;
|
||||
function byteSwap32(arr) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arr[i] = byteSwap(arr[i]);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
var swap32IfBE = isLE ? (u) => u : byteSwap32;
|
||||
function utf8ToBytes(str) {
|
||||
if (typeof str !== "string")
|
||||
throw new TypeError("string expected");
|
||||
return new Uint8Array(new TextEncoder().encode(str));
|
||||
}
|
||||
function kdfInputToBytes(data, errorTitle = "") {
|
||||
if (typeof data === "string")
|
||||
return utf8ToBytes(data);
|
||||
return abytes(data, void 0, errorTitle);
|
||||
}
|
||||
function createHasher(hashCons, info = {}) {
|
||||
const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
|
||||
const tmp = hashCons(void 0);
|
||||
hashC.outputLen = tmp.outputLen;
|
||||
hashC.blockLen = tmp.blockLen;
|
||||
hashC.canXOF = tmp.canXOF;
|
||||
hashC.create = (opts) => hashCons(opts);
|
||||
Object.assign(hashC, info);
|
||||
return Object.freeze(hashC);
|
||||
}
|
||||
|
||||
// node_modules/@noble/hashes/_blake.js
|
||||
var BSIGMA = /* @__PURE__ */ Uint8Array.from([
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
14,
|
||||
10,
|
||||
4,
|
||||
8,
|
||||
9,
|
||||
15,
|
||||
13,
|
||||
6,
|
||||
1,
|
||||
12,
|
||||
0,
|
||||
2,
|
||||
11,
|
||||
7,
|
||||
5,
|
||||
3,
|
||||
11,
|
||||
8,
|
||||
12,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
15,
|
||||
13,
|
||||
10,
|
||||
14,
|
||||
3,
|
||||
6,
|
||||
7,
|
||||
1,
|
||||
9,
|
||||
4,
|
||||
7,
|
||||
9,
|
||||
3,
|
||||
1,
|
||||
13,
|
||||
12,
|
||||
11,
|
||||
14,
|
||||
2,
|
||||
6,
|
||||
5,
|
||||
10,
|
||||
4,
|
||||
0,
|
||||
15,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
7,
|
||||
2,
|
||||
4,
|
||||
10,
|
||||
15,
|
||||
14,
|
||||
1,
|
||||
11,
|
||||
12,
|
||||
6,
|
||||
8,
|
||||
3,
|
||||
13,
|
||||
2,
|
||||
12,
|
||||
6,
|
||||
10,
|
||||
0,
|
||||
11,
|
||||
8,
|
||||
3,
|
||||
4,
|
||||
13,
|
||||
7,
|
||||
5,
|
||||
15,
|
||||
14,
|
||||
1,
|
||||
9,
|
||||
12,
|
||||
5,
|
||||
1,
|
||||
15,
|
||||
14,
|
||||
13,
|
||||
4,
|
||||
10,
|
||||
0,
|
||||
7,
|
||||
6,
|
||||
3,
|
||||
9,
|
||||
2,
|
||||
8,
|
||||
11,
|
||||
13,
|
||||
11,
|
||||
7,
|
||||
14,
|
||||
12,
|
||||
1,
|
||||
3,
|
||||
9,
|
||||
5,
|
||||
0,
|
||||
15,
|
||||
4,
|
||||
8,
|
||||
6,
|
||||
2,
|
||||
10,
|
||||
6,
|
||||
15,
|
||||
14,
|
||||
9,
|
||||
11,
|
||||
3,
|
||||
0,
|
||||
8,
|
||||
12,
|
||||
2,
|
||||
13,
|
||||
7,
|
||||
1,
|
||||
4,
|
||||
10,
|
||||
5,
|
||||
10,
|
||||
2,
|
||||
8,
|
||||
4,
|
||||
7,
|
||||
6,
|
||||
1,
|
||||
5,
|
||||
15,
|
||||
11,
|
||||
9,
|
||||
14,
|
||||
3,
|
||||
12,
|
||||
13,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
14,
|
||||
10,
|
||||
4,
|
||||
8,
|
||||
9,
|
||||
15,
|
||||
13,
|
||||
6,
|
||||
1,
|
||||
12,
|
||||
0,
|
||||
2,
|
||||
11,
|
||||
7,
|
||||
5,
|
||||
3,
|
||||
// Blake1, unused in others
|
||||
11,
|
||||
8,
|
||||
12,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
15,
|
||||
13,
|
||||
10,
|
||||
14,
|
||||
3,
|
||||
6,
|
||||
7,
|
||||
1,
|
||||
9,
|
||||
4,
|
||||
7,
|
||||
9,
|
||||
3,
|
||||
1,
|
||||
13,
|
||||
12,
|
||||
11,
|
||||
14,
|
||||
2,
|
||||
6,
|
||||
5,
|
||||
10,
|
||||
4,
|
||||
0,
|
||||
15,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
7,
|
||||
2,
|
||||
4,
|
||||
10,
|
||||
15,
|
||||
14,
|
||||
1,
|
||||
11,
|
||||
12,
|
||||
6,
|
||||
8,
|
||||
3,
|
||||
13,
|
||||
2,
|
||||
12,
|
||||
6,
|
||||
10,
|
||||
0,
|
||||
11,
|
||||
8,
|
||||
3,
|
||||
4,
|
||||
13,
|
||||
7,
|
||||
5,
|
||||
15,
|
||||
14,
|
||||
1,
|
||||
9
|
||||
]);
|
||||
|
||||
// node_modules/@noble/hashes/blake2.js
|
||||
var B2B_IV = /* @__PURE__ */ Uint32Array.from([
|
||||
4089235720,
|
||||
1779033703,
|
||||
2227873595,
|
||||
3144134277,
|
||||
4271175723,
|
||||
1013904242,
|
||||
1595750129,
|
||||
2773480762,
|
||||
2917565137,
|
||||
1359893119,
|
||||
725511199,
|
||||
2600822924,
|
||||
4215389547,
|
||||
528734635,
|
||||
327033209,
|
||||
1541459225
|
||||
]);
|
||||
var BBUF = /* @__PURE__ */ new Uint32Array(32);
|
||||
function G1b(a, b, c, d, msg, x) {
|
||||
const Xl = msg[x], Xh = msg[x + 1];
|
||||
let Al = BBUF[2 * a], Ah = BBUF[2 * a + 1];
|
||||
let Bl = BBUF[2 * b], Bh = BBUF[2 * b + 1];
|
||||
let Cl = BBUF[2 * c], Ch = BBUF[2 * c + 1];
|
||||
let Dl = BBUF[2 * d], Dh = BBUF[2 * d + 1];
|
||||
let ll = add3L(Al, Bl, Xl);
|
||||
Ah = add3H(ll, Ah, Bh, Xh);
|
||||
Al = ll | 0;
|
||||
({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
|
||||
({ Dh, Dl } = { Dh: rotr32H(Dh, Dl), Dl: rotr32L(Dh, Dl) });
|
||||
({ h: Ch, l: Cl } = add(Ch, Cl, Dh, Dl));
|
||||
({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
|
||||
({ Bh, Bl } = { Bh: rotrSH(Bh, Bl, 24), Bl: rotrSL(Bh, Bl, 24) });
|
||||
BBUF[2 * a] = Al, BBUF[2 * a + 1] = Ah;
|
||||
BBUF[2 * b] = Bl, BBUF[2 * b + 1] = Bh;
|
||||
BBUF[2 * c] = Cl, BBUF[2 * c + 1] = Ch;
|
||||
BBUF[2 * d] = Dl, BBUF[2 * d + 1] = Dh;
|
||||
}
|
||||
function G2b(a, b, c, d, msg, x) {
|
||||
const Xl = msg[x], Xh = msg[x + 1];
|
||||
let Al = BBUF[2 * a], Ah = BBUF[2 * a + 1];
|
||||
let Bl = BBUF[2 * b], Bh = BBUF[2 * b + 1];
|
||||
let Cl = BBUF[2 * c], Ch = BBUF[2 * c + 1];
|
||||
let Dl = BBUF[2 * d], Dh = BBUF[2 * d + 1];
|
||||
let ll = add3L(Al, Bl, Xl);
|
||||
Ah = add3H(ll, Ah, Bh, Xh);
|
||||
Al = ll | 0;
|
||||
({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
|
||||
({ Dh, Dl } = { Dh: rotrSH(Dh, Dl, 16), Dl: rotrSL(Dh, Dl, 16) });
|
||||
({ h: Ch, l: Cl } = add(Ch, Cl, Dh, Dl));
|
||||
({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
|
||||
({ Bh, Bl } = { Bh: rotrBH(Bh, Bl, 63), Bl: rotrBL(Bh, Bl, 63) });
|
||||
BBUF[2 * a] = Al, BBUF[2 * a + 1] = Ah;
|
||||
BBUF[2 * b] = Bl, BBUF[2 * b + 1] = Bh;
|
||||
BBUF[2 * c] = Cl, BBUF[2 * c + 1] = Ch;
|
||||
BBUF[2 * d] = Dl, BBUF[2 * d + 1] = Dh;
|
||||
}
|
||||
function checkBlake2Opts(outputLen, opts = {}, keyLen, saltLen, persLen) {
|
||||
anumber(keyLen);
|
||||
if (outputLen <= 0 || outputLen > keyLen)
|
||||
throw new Error("outputLen bigger than keyLen");
|
||||
const { key, salt, personalization } = opts;
|
||||
if (key !== void 0 && (key.length < 1 || key.length > keyLen))
|
||||
throw new Error('"key" expected to be undefined or of length=1..' + keyLen);
|
||||
if (salt !== void 0)
|
||||
abytes(salt, saltLen, "salt");
|
||||
if (personalization !== void 0)
|
||||
abytes(personalization, persLen, "personalization");
|
||||
}
|
||||
var _BLAKE2 = class {
|
||||
constructor(blockLen, outputLen) {
|
||||
__publicField(this, "buffer");
|
||||
__publicField(this, "buffer32");
|
||||
__publicField(this, "finished", false);
|
||||
__publicField(this, "destroyed", false);
|
||||
__publicField(this, "length", 0);
|
||||
__publicField(this, "pos", 0);
|
||||
__publicField(this, "blockLen");
|
||||
__publicField(this, "outputLen");
|
||||
__publicField(this, "canXOF", false);
|
||||
anumber(blockLen);
|
||||
anumber(outputLen);
|
||||
this.blockLen = blockLen;
|
||||
this.outputLen = outputLen;
|
||||
this.buffer = new Uint8Array(blockLen);
|
||||
this.buffer32 = u32(this.buffer);
|
||||
}
|
||||
update(data) {
|
||||
aexists(this);
|
||||
abytes(data);
|
||||
const { blockLen, buffer, buffer32 } = this;
|
||||
const len = data.length;
|
||||
const offset = data.byteOffset;
|
||||
const buf = data.buffer;
|
||||
for (let pos = 0; pos < len; ) {
|
||||
if (this.pos === blockLen) {
|
||||
swap32IfBE(buffer32);
|
||||
this.compress(buffer32, 0, false);
|
||||
swap32IfBE(buffer32);
|
||||
this.pos = 0;
|
||||
}
|
||||
const take = Math.min(blockLen - this.pos, len - pos);
|
||||
const dataOffset = offset + pos;
|
||||
if (take === blockLen && !(dataOffset % 4) && pos + take < len) {
|
||||
const data32 = new Uint32Array(buf, dataOffset, Math.floor((len - pos) / 4));
|
||||
swap32IfBE(data32);
|
||||
for (let pos32 = 0; pos + blockLen < len; pos32 += buffer32.length, pos += blockLen) {
|
||||
this.length += blockLen;
|
||||
this.compress(data32, pos32, false);
|
||||
}
|
||||
swap32IfBE(data32);
|
||||
continue;
|
||||
}
|
||||
buffer.set(data.subarray(pos, pos + take), this.pos);
|
||||
this.pos += take;
|
||||
this.length += take;
|
||||
pos += take;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
digestInto(out) {
|
||||
aexists(this);
|
||||
aoutput(out, this);
|
||||
const { pos, buffer32 } = this;
|
||||
this.finished = true;
|
||||
clean(this.buffer.subarray(pos));
|
||||
swap32IfBE(buffer32);
|
||||
this.compress(buffer32, 0, true);
|
||||
swap32IfBE(buffer32);
|
||||
if (out.byteOffset & 3)
|
||||
throw new RangeError('"digestInto() output" expected 4-byte aligned byteOffset, got ' + out.byteOffset);
|
||||
const state = this.get();
|
||||
const out32 = u32(out);
|
||||
const full = Math.floor(this.outputLen / 4);
|
||||
for (let i = 0; i < full; i++)
|
||||
out32[i] = swap8IfBE(state[i]);
|
||||
const tail = this.outputLen % 4;
|
||||
if (!tail)
|
||||
return;
|
||||
const off = full * 4;
|
||||
const word = state[full];
|
||||
for (let i = 0; i < tail; i++)
|
||||
out[off + i] = word >>> 8 * i;
|
||||
}
|
||||
digest() {
|
||||
const { buffer, outputLen } = this;
|
||||
this.digestInto(buffer);
|
||||
const res = buffer.slice(0, outputLen);
|
||||
this.destroy();
|
||||
return res;
|
||||
}
|
||||
_cloneInto(to) {
|
||||
const { buffer, length, finished, destroyed, outputLen, pos } = this;
|
||||
to || (to = new this.constructor({ dkLen: outputLen }));
|
||||
to.set(...this.get());
|
||||
to.buffer.set(buffer);
|
||||
to.destroyed = destroyed;
|
||||
to.finished = finished;
|
||||
to.length = length;
|
||||
to.pos = pos;
|
||||
to.outputLen = outputLen;
|
||||
return to;
|
||||
}
|
||||
clone() {
|
||||
return this._cloneInto();
|
||||
}
|
||||
};
|
||||
var _BLAKE2b = class extends _BLAKE2 {
|
||||
constructor(opts = {}) {
|
||||
const olen = opts.dkLen === void 0 ? 64 : opts.dkLen;
|
||||
super(128, olen);
|
||||
// Same IV words as SHA-512 / BLAKE2b, encoded as LE u32 low/high halves.
|
||||
__publicField(this, "v0l", B2B_IV[0] | 0);
|
||||
__publicField(this, "v0h", B2B_IV[1] | 0);
|
||||
__publicField(this, "v1l", B2B_IV[2] | 0);
|
||||
__publicField(this, "v1h", B2B_IV[3] | 0);
|
||||
__publicField(this, "v2l", B2B_IV[4] | 0);
|
||||
__publicField(this, "v2h", B2B_IV[5] | 0);
|
||||
__publicField(this, "v3l", B2B_IV[6] | 0);
|
||||
__publicField(this, "v3h", B2B_IV[7] | 0);
|
||||
__publicField(this, "v4l", B2B_IV[8] | 0);
|
||||
__publicField(this, "v4h", B2B_IV[9] | 0);
|
||||
__publicField(this, "v5l", B2B_IV[10] | 0);
|
||||
__publicField(this, "v5h", B2B_IV[11] | 0);
|
||||
__publicField(this, "v6l", B2B_IV[12] | 0);
|
||||
__publicField(this, "v6h", B2B_IV[13] | 0);
|
||||
__publicField(this, "v7l", B2B_IV[14] | 0);
|
||||
__publicField(this, "v7h", B2B_IV[15] | 0);
|
||||
checkBlake2Opts(olen, opts, 64, 16, 16);
|
||||
let { key, personalization, salt } = opts;
|
||||
let keyLength = 0;
|
||||
if (key !== void 0) {
|
||||
abytes(key, void 0, "key");
|
||||
keyLength = key.length;
|
||||
}
|
||||
this.v0l ^= this.outputLen | keyLength << 8 | 1 << 16 | 1 << 24;
|
||||
if (salt !== void 0) {
|
||||
abytes(salt, void 0, "salt");
|
||||
const slt = u32(salt);
|
||||
this.v4l ^= swap8IfBE(slt[0]);
|
||||
this.v4h ^= swap8IfBE(slt[1]);
|
||||
this.v5l ^= swap8IfBE(slt[2]);
|
||||
this.v5h ^= swap8IfBE(slt[3]);
|
||||
}
|
||||
if (personalization !== void 0) {
|
||||
abytes(personalization, void 0, "personalization");
|
||||
const pers = u32(personalization);
|
||||
this.v6l ^= swap8IfBE(pers[0]);
|
||||
this.v6h ^= swap8IfBE(pers[1]);
|
||||
this.v7l ^= swap8IfBE(pers[2]);
|
||||
this.v7h ^= swap8IfBE(pers[3]);
|
||||
}
|
||||
if (key !== void 0) {
|
||||
const tmp = new Uint8Array(this.blockLen);
|
||||
tmp.set(key);
|
||||
this.update(tmp);
|
||||
}
|
||||
}
|
||||
// prettier-ignore
|
||||
get() {
|
||||
let { v0l, v0h, v1l, v1h, v2l, v2h, v3l, v3h, v4l, v4h, v5l, v5h, v6l, v6h, v7l, v7h } = this;
|
||||
return [v0l, v0h, v1l, v1h, v2l, v2h, v3l, v3h, v4l, v4h, v5l, v5h, v6l, v6h, v7l, v7h];
|
||||
}
|
||||
// prettier-ignore
|
||||
set(v0l, v0h, v1l, v1h, v2l, v2h, v3l, v3h, v4l, v4h, v5l, v5h, v6l, v6h, v7l, v7h) {
|
||||
this.v0l = v0l | 0;
|
||||
this.v0h = v0h | 0;
|
||||
this.v1l = v1l | 0;
|
||||
this.v1h = v1h | 0;
|
||||
this.v2l = v2l | 0;
|
||||
this.v2h = v2h | 0;
|
||||
this.v3l = v3l | 0;
|
||||
this.v3h = v3h | 0;
|
||||
this.v4l = v4l | 0;
|
||||
this.v4h = v4h | 0;
|
||||
this.v5l = v5l | 0;
|
||||
this.v5h = v5h | 0;
|
||||
this.v6l = v6l | 0;
|
||||
this.v6h = v6h | 0;
|
||||
this.v7l = v7l | 0;
|
||||
this.v7h = v7h | 0;
|
||||
}
|
||||
compress(msg, offset, isLast) {
|
||||
this.get().forEach((v, i) => BBUF[i] = v);
|
||||
BBUF.set(B2B_IV, 16);
|
||||
let { h, l } = fromBig(BigInt(this.length));
|
||||
BBUF[24] = B2B_IV[8] ^ l;
|
||||
BBUF[25] = B2B_IV[9] ^ h;
|
||||
if (isLast) {
|
||||
BBUF[28] = ~BBUF[28];
|
||||
BBUF[29] = ~BBUF[29];
|
||||
}
|
||||
let j = 0;
|
||||
const s = BSIGMA;
|
||||
for (let i = 0; i < 12; i++) {
|
||||
G1b(0, 4, 8, 12, msg, offset + 2 * s[j++]);
|
||||
G2b(0, 4, 8, 12, msg, offset + 2 * s[j++]);
|
||||
G1b(1, 5, 9, 13, msg, offset + 2 * s[j++]);
|
||||
G2b(1, 5, 9, 13, msg, offset + 2 * s[j++]);
|
||||
G1b(2, 6, 10, 14, msg, offset + 2 * s[j++]);
|
||||
G2b(2, 6, 10, 14, msg, offset + 2 * s[j++]);
|
||||
G1b(3, 7, 11, 15, msg, offset + 2 * s[j++]);
|
||||
G2b(3, 7, 11, 15, msg, offset + 2 * s[j++]);
|
||||
G1b(0, 5, 10, 15, msg, offset + 2 * s[j++]);
|
||||
G2b(0, 5, 10, 15, msg, offset + 2 * s[j++]);
|
||||
G1b(1, 6, 11, 12, msg, offset + 2 * s[j++]);
|
||||
G2b(1, 6, 11, 12, msg, offset + 2 * s[j++]);
|
||||
G1b(2, 7, 8, 13, msg, offset + 2 * s[j++]);
|
||||
G2b(2, 7, 8, 13, msg, offset + 2 * s[j++]);
|
||||
G1b(3, 4, 9, 14, msg, offset + 2 * s[j++]);
|
||||
G2b(3, 4, 9, 14, msg, offset + 2 * s[j++]);
|
||||
}
|
||||
this.v0l ^= BBUF[0] ^ BBUF[16];
|
||||
this.v0h ^= BBUF[1] ^ BBUF[17];
|
||||
this.v1l ^= BBUF[2] ^ BBUF[18];
|
||||
this.v1h ^= BBUF[3] ^ BBUF[19];
|
||||
this.v2l ^= BBUF[4] ^ BBUF[20];
|
||||
this.v2h ^= BBUF[5] ^ BBUF[21];
|
||||
this.v3l ^= BBUF[6] ^ BBUF[22];
|
||||
this.v3h ^= BBUF[7] ^ BBUF[23];
|
||||
this.v4l ^= BBUF[8] ^ BBUF[24];
|
||||
this.v4h ^= BBUF[9] ^ BBUF[25];
|
||||
this.v5l ^= BBUF[10] ^ BBUF[26];
|
||||
this.v5h ^= BBUF[11] ^ BBUF[27];
|
||||
this.v6l ^= BBUF[12] ^ BBUF[28];
|
||||
this.v6h ^= BBUF[13] ^ BBUF[29];
|
||||
this.v7l ^= BBUF[14] ^ BBUF[30];
|
||||
this.v7h ^= BBUF[15] ^ BBUF[31];
|
||||
clean(BBUF);
|
||||
}
|
||||
destroy() {
|
||||
this.destroyed = true;
|
||||
clean(this.buffer32);
|
||||
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
};
|
||||
var blake2b = /* @__PURE__ */ createHasher((opts) => new _BLAKE2b(opts));
|
||||
|
||||
// node_modules/@noble/hashes/argon2.js
|
||||
var AT = { Argond2d: 0, Argon2i: 1, Argon2id: 2 };
|
||||
var ARGON2_SYNC_POINTS = 4;
|
||||
var abytesOrZero = (buf, errorTitle = "") => {
|
||||
if (buf === void 0)
|
||||
return Uint8Array.of();
|
||||
return kdfInputToBytes(buf, errorTitle);
|
||||
};
|
||||
function mul(a, b) {
|
||||
const aL = a & 65535;
|
||||
const aH = a >>> 16;
|
||||
const bL = b & 65535;
|
||||
const bH = b >>> 16;
|
||||
const ll = Math.imul(aL, bL);
|
||||
const hl = Math.imul(aH, bL);
|
||||
const lh = Math.imul(aL, bH);
|
||||
const hh = Math.imul(aH, bH);
|
||||
const carry = (ll >>> 16) + (hl & 65535) + lh;
|
||||
const high = hh + (hl >>> 16) + (carry >>> 16) | 0;
|
||||
const low = carry << 16 | ll & 65535;
|
||||
return { h: high, l: low };
|
||||
}
|
||||
function mul2(a, b) {
|
||||
const { h, l } = mul(a, b);
|
||||
return { h: (h << 1 | l >>> 31) & 4294967295, l: l << 1 & 4294967295 };
|
||||
}
|
||||
function blamka(Ah, Al, Bh, Bl) {
|
||||
const { h: Ch, l: Cl } = mul2(Al, Bl);
|
||||
const Rll = add3L(Al, Bl, Cl);
|
||||
return { h: add3H(Rll, Ah, Bh, Ch), l: Rll | 0 };
|
||||
}
|
||||
var A2_BUF = new Uint32Array(256);
|
||||
function G(a, b, c, d) {
|
||||
let Al = A2_BUF[2 * a], Ah = A2_BUF[2 * a + 1];
|
||||
let Bl = A2_BUF[2 * b], Bh = A2_BUF[2 * b + 1];
|
||||
let Cl = A2_BUF[2 * c], Ch = A2_BUF[2 * c + 1];
|
||||
let Dl = A2_BUF[2 * d], Dh = A2_BUF[2 * d + 1];
|
||||
({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl));
|
||||
({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
|
||||
({ Dh, Dl } = { Dh: rotr32H(Dh, Dl), Dl: rotr32L(Dh, Dl) });
|
||||
({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl));
|
||||
({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
|
||||
({ Bh, Bl } = { Bh: rotrSH(Bh, Bl, 24), Bl: rotrSL(Bh, Bl, 24) });
|
||||
({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl));
|
||||
({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
|
||||
({ Dh, Dl } = { Dh: rotrSH(Dh, Dl, 16), Dl: rotrSL(Dh, Dl, 16) });
|
||||
({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl));
|
||||
({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
|
||||
({ Bh, Bl } = { Bh: rotrBH(Bh, Bl, 63), Bl: rotrBL(Bh, Bl, 63) });
|
||||
A2_BUF[2 * a] = Al, A2_BUF[2 * a + 1] = Ah;
|
||||
A2_BUF[2 * b] = Bl, A2_BUF[2 * b + 1] = Bh;
|
||||
A2_BUF[2 * c] = Cl, A2_BUF[2 * c + 1] = Ch;
|
||||
A2_BUF[2 * d] = Dl, A2_BUF[2 * d + 1] = Dh;
|
||||
}
|
||||
function P(v00, v01, v02, v03, v04, v05, v06, v07, v08, v09, v10, v11, v12, v13, v14, v15) {
|
||||
G(v00, v04, v08, v12);
|
||||
G(v01, v05, v09, v13);
|
||||
G(v02, v06, v10, v14);
|
||||
G(v03, v07, v11, v15);
|
||||
G(v00, v05, v10, v15);
|
||||
G(v01, v06, v11, v12);
|
||||
G(v02, v07, v08, v13);
|
||||
G(v03, v04, v09, v14);
|
||||
}
|
||||
function block(x, xPos, yPos, outPos, needXor) {
|
||||
for (let i = 0; i < 256; i++)
|
||||
A2_BUF[i] = x[xPos + i] ^ x[yPos + i];
|
||||
for (let i = 0; i < 128; i += 16) {
|
||||
P(i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, i + 8, i + 9, i + 10, i + 11, i + 12, i + 13, i + 14, i + 15);
|
||||
}
|
||||
for (let i = 0; i < 16; i += 2) {
|
||||
P(i, i + 1, i + 16, i + 17, i + 32, i + 33, i + 48, i + 49, i + 64, i + 65, i + 80, i + 81, i + 96, i + 97, i + 112, i + 113);
|
||||
}
|
||||
if (needXor)
|
||||
for (let i = 0; i < 256; i++)
|
||||
x[outPos + i] ^= A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
|
||||
else
|
||||
for (let i = 0; i < 256; i++)
|
||||
x[outPos + i] = A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
|
||||
clean(A2_BUF);
|
||||
}
|
||||
function Hp(A, dkLen) {
|
||||
const A8 = u8(A);
|
||||
const T = new Uint32Array(1);
|
||||
const T8 = u8(T);
|
||||
T[0] = swap8IfBE(dkLen);
|
||||
if (dkLen <= 64)
|
||||
return blake2b.create({ dkLen }).update(T8).update(A8).digest();
|
||||
const out = new Uint8Array(dkLen);
|
||||
let V = blake2b.create({}).update(T8).update(A8).digest();
|
||||
let pos = 0;
|
||||
out.set(V.subarray(0, 32));
|
||||
pos += 32;
|
||||
for (; dkLen - pos > 64; pos += 32) {
|
||||
const Vh = blake2b.create({}).update(V);
|
||||
Vh.digestInto(V);
|
||||
Vh.destroy();
|
||||
out.set(V.subarray(0, 32), pos);
|
||||
}
|
||||
out.set(blake2b(V, { dkLen: dkLen - pos }), pos);
|
||||
clean(V, T);
|
||||
return out;
|
||||
}
|
||||
function indexAlpha(r, s, laneLen, segmentLen, index, randL, sameLane = false) {
|
||||
let area;
|
||||
if (r === 0) {
|
||||
if (s === 0)
|
||||
area = index - 1;
|
||||
else if (sameLane)
|
||||
area = s * segmentLen + index - 1;
|
||||
else
|
||||
area = s * segmentLen + (index == 0 ? -1 : 0);
|
||||
} else if (sameLane)
|
||||
area = laneLen - segmentLen + index - 1;
|
||||
else
|
||||
area = laneLen - segmentLen + (index == 0 ? -1 : 0);
|
||||
const startPos = r !== 0 && s !== ARGON2_SYNC_POINTS - 1 ? (s + 1) * segmentLen : 0;
|
||||
const rel = area - 1 - mul(area, mul(randL, randL).h).h;
|
||||
return (startPos + rel) % laneLen;
|
||||
}
|
||||
var maxUint32 = Math.pow(2, 32);
|
||||
function isU32(num) {
|
||||
return Number.isSafeInteger(num) && num >= 0 && num < maxUint32;
|
||||
}
|
||||
function argon2Opts(opts) {
|
||||
const merged = {
|
||||
version: 19,
|
||||
dkLen: 32,
|
||||
maxmem: maxUint32 - 1,
|
||||
asyncTick: 10
|
||||
};
|
||||
for (let [k, v] of Object.entries(opts))
|
||||
if (v !== void 0)
|
||||
merged[k] = v;
|
||||
const { dkLen, p, m, t, version, onProgress, asyncTick } = merged;
|
||||
if (!isU32(dkLen) || dkLen < 4)
|
||||
throw new Error('"dkLen" must be 4..');
|
||||
if (!isU32(p) || p < 1 || p >= Math.pow(2, 24))
|
||||
throw new Error('"p" must be 1..2^24');
|
||||
if (!isU32(m))
|
||||
throw new Error('"m" must be 0..2^32');
|
||||
if (!isU32(t) || t < 1)
|
||||
throw new Error('"t" (iterations) must be 1..2^32');
|
||||
if (onProgress !== void 0 && typeof onProgress !== "function")
|
||||
throw new Error('"progressCb" must be a function');
|
||||
anumber(asyncTick, "asyncTick");
|
||||
if (!isU32(m) || m < 8 * p)
|
||||
throw new Error('"m" (memory) must be at least 8*p bytes');
|
||||
if (version !== 16 && version !== 19)
|
||||
throw new Error('"version" must be 0x10 or 0x13, got ' + version);
|
||||
return merged;
|
||||
}
|
||||
function argon2Init(password, salt, type, opts) {
|
||||
password = kdfInputToBytes(password, "password");
|
||||
salt = kdfInputToBytes(salt, "salt");
|
||||
if (!isU32(password.length))
|
||||
throw new Error('"password" must be less of length 1..4Gb');
|
||||
if (!isU32(salt.length) || salt.length < 8)
|
||||
throw new Error('"salt" must be of length 8..4Gb');
|
||||
if (!Object.values(AT).includes(type))
|
||||
throw new Error('"type" was invalid');
|
||||
let { p, dkLen, m, t, version, key, personalization, maxmem, onProgress, asyncTick } = argon2Opts(opts);
|
||||
key = abytesOrZero(key, "key");
|
||||
personalization = abytesOrZero(personalization, "personalization");
|
||||
const h = blake2b.create();
|
||||
const BUF = new Uint32Array(1);
|
||||
const BUF8 = u8(BUF);
|
||||
for (let item of [p, dkLen, m, t, version, type]) {
|
||||
BUF[0] = swap8IfBE(item);
|
||||
h.update(BUF8);
|
||||
}
|
||||
for (let i of [password, salt, key, personalization]) {
|
||||
BUF[0] = swap8IfBE(i.length);
|
||||
h.update(BUF8).update(i);
|
||||
}
|
||||
const H0 = new Uint32Array(18);
|
||||
const H0_8 = u8(H0);
|
||||
h.digestInto(H0_8);
|
||||
const lanes = p;
|
||||
const mP = 4 * p * Math.floor(m / (ARGON2_SYNC_POINTS * p));
|
||||
const laneLen = Math.floor(mP / p);
|
||||
const segmentLen = Math.floor(laneLen / ARGON2_SYNC_POINTS);
|
||||
const memUsed = mP * 1024;
|
||||
if (!isU32(maxmem))
|
||||
throw new Error('"maxmem" expected <2**32, got ' + maxmem);
|
||||
if (memUsed > maxmem)
|
||||
throw new Error('"maxmem" limit was hit: memUsed(mP*1024)=' + memUsed + ", maxmem=" + maxmem);
|
||||
const B = new Uint32Array(memUsed / 4);
|
||||
for (let l = 0; l < p; l++) {
|
||||
const i = 256 * laneLen * l;
|
||||
H0[17] = swap8IfBE(l);
|
||||
H0[16] = swap8IfBE(0);
|
||||
B.set(swap32IfBE(u32(Hp(H0, 1024))), i);
|
||||
H0[16] = swap8IfBE(1);
|
||||
B.set(swap32IfBE(u32(Hp(H0, 1024))), i + 256);
|
||||
}
|
||||
let perBlock = () => {
|
||||
};
|
||||
if (onProgress) {
|
||||
const totalBlock = t * ARGON2_SYNC_POINTS * p * segmentLen - 2 * p;
|
||||
const callbackPer = Math.max(Math.floor(totalBlock / 1e4), 1);
|
||||
let blockCnt = 0;
|
||||
perBlock = () => {
|
||||
blockCnt++;
|
||||
if (onProgress && (!(blockCnt % callbackPer) || blockCnt === totalBlock))
|
||||
onProgress(blockCnt / totalBlock);
|
||||
};
|
||||
}
|
||||
clean(BUF, H0);
|
||||
return { type, mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock, asyncTick };
|
||||
}
|
||||
function argon2Output(B, p, laneLen, dkLen) {
|
||||
const B_final = new Uint32Array(256);
|
||||
for (let l = 0; l < p; l++)
|
||||
for (let j = 0; j < 256; j++)
|
||||
B_final[j] ^= B[256 * (laneLen * l + laneLen - 1) + j];
|
||||
const res = Hp(swap32IfBE(B_final), dkLen);
|
||||
clean(B, B_final);
|
||||
return res;
|
||||
}
|
||||
function processBlock(B, address, l, r, s, index, laneLen, segmentLen, lanes, offset, prev, dataIndependent, needXor) {
|
||||
if (offset % laneLen)
|
||||
prev = offset - 1;
|
||||
let randL, randH;
|
||||
if (dataIndependent) {
|
||||
let i128 = index % 128;
|
||||
if (i128 === 0) {
|
||||
address[256 + 12]++;
|
||||
block(address, 256, 2 * 256, 0, false);
|
||||
block(address, 0, 2 * 256, 0, false);
|
||||
}
|
||||
randL = address[2 * i128];
|
||||
randH = address[2 * i128 + 1];
|
||||
} else {
|
||||
const T = 256 * prev;
|
||||
randL = B[T];
|
||||
randH = B[T + 1];
|
||||
}
|
||||
const refLane = r === 0 && s === 0 ? l : randH % lanes;
|
||||
const refPos = indexAlpha(r, s, laneLen, segmentLen, index, randL, refLane == l);
|
||||
const refBlock = laneLen * refLane + refPos;
|
||||
block(B, 256 * prev, 256 * refBlock, offset * 256, needXor);
|
||||
}
|
||||
function argon2(type, password, salt, opts) {
|
||||
const { mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock } = argon2Init(password, salt, type, opts);
|
||||
const address = new Uint32Array(3 * 256);
|
||||
address[256 + 6] = mP;
|
||||
address[256 + 8] = t;
|
||||
address[256 + 10] = type;
|
||||
for (let r = 0; r < t; r++) {
|
||||
const needXor = r !== 0 && version === 19;
|
||||
address[256 + 0] = r;
|
||||
for (let s = 0; s < ARGON2_SYNC_POINTS; s++) {
|
||||
address[256 + 4] = s;
|
||||
const dataIndependent = type == AT.Argon2i || type == AT.Argon2id && r === 0 && s < 2;
|
||||
for (let l = 0; l < p; l++) {
|
||||
address[256 + 2] = l;
|
||||
address[256 + 12] = 0;
|
||||
let startPos = 0;
|
||||
if (r === 0 && s === 0) {
|
||||
startPos = 2;
|
||||
if (dataIndependent) {
|
||||
address[256 + 12]++;
|
||||
block(address, 256, 2 * 256, 0, false);
|
||||
block(address, 0, 2 * 256, 0, false);
|
||||
}
|
||||
}
|
||||
let offset = l * laneLen + s * segmentLen + startPos;
|
||||
let prev = offset % laneLen ? offset - 1 : offset + laneLen - 1;
|
||||
for (let index = startPos; index < segmentLen; index++, offset++, prev++) {
|
||||
perBlock();
|
||||
processBlock(B, address, l, r, s, index, laneLen, segmentLen, lanes, offset, prev, dataIndependent, needXor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clean(address);
|
||||
return argon2Output(B, p, laneLen, dkLen);
|
||||
}
|
||||
var argon2id = (password, salt, opts) => argon2(AT.Argon2id, password, salt, opts);
|
||||
|
||||
// entry.js
|
||||
globalThis.NobleArgon2 = { argon2id };
|
||||
})();
|
||||
@@ -69,6 +69,67 @@ test('deriveKeyAndVerifier: v2 verifier is decoupled SHA-256(keyHex + domain)',
|
||||
assert.notEqual(verifier, keyHex, 'v2 verifier must not leak the AES key');
|
||||
});
|
||||
|
||||
// ---- Argon2id KDF (js/argon2.js — vendored @noble/hashes) -------------------
|
||||
|
||||
test('vendored argon2id matches the RFC 9106 §5.3 test vector', () => {
|
||||
// Independent cross-check that the bundled library is correct (not just
|
||||
// self-consistent). Same vector used to validate the vendored bundle.
|
||||
const out = T.NobleArgon2.argon2id(
|
||||
new Uint8Array(32).fill(1), new Uint8Array(16).fill(2),
|
||||
{ t: 3, m: 32, p: 4, dkLen: 32,
|
||||
key: new Uint8Array(8).fill(3),
|
||||
personalization: new Uint8Array(12).fill(4), version: 0x13 });
|
||||
assert.equal(T.bytesToHex(out),
|
||||
'0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659');
|
||||
});
|
||||
|
||||
test('isDecoupledVerifierAlgo: all -v2 markers decouple, legacy does not', () => {
|
||||
assert.equal(T.isDecoupledVerifierAlgo(T.HASH_ALGO_V2), true);
|
||||
assert.equal(T.isDecoupledVerifierAlgo(T.HASH_ALGO_ARGON2), true);
|
||||
assert.equal(T.isDecoupledVerifierAlgo('pbkdf2-sha256'), false);
|
||||
assert.equal(T.isDecoupledVerifierAlgo('pbkdf2'), false);
|
||||
assert.equal(T.isDecoupledVerifierAlgo(''), false);
|
||||
});
|
||||
|
||||
test('deriveKeyAndVerifier: argon2id branch derives an Argon2 key, NOT PBKDF2', async () => {
|
||||
const pwd = 'correct horse battery staple';
|
||||
const saltHex = 'a1b2c3d4e5f6a1b2';
|
||||
|
||||
// The raw key bytes must equal argon2id(pwd, saltHex-utf8, params) — cross
|
||||
// checked against the vendored lib directly (small params for test speed).
|
||||
const params = { m: 256, t: 1, p: 1 };
|
||||
const expectKey = T.bytesToHex(T.NobleArgon2.argon2id(
|
||||
new TextEncoder().encode(pwd), new TextEncoder().encode(saltHex),
|
||||
{ t: params.t, m: params.m, p: params.p, dkLen: 32, version: 0x13 }));
|
||||
|
||||
const { cryptoKey, verifier } = await T.deriveKeyAndVerifier(
|
||||
pwd, saltHex, 0, T.HASH_ALGO_ARGON2, params);
|
||||
const raw = await ctx.crypto.subtle.exportKey('raw', cryptoKey);
|
||||
assert.equal(T.bytesToHex(new Uint8Array(raw)), expectKey, 'AES key must be the Argon2id output');
|
||||
|
||||
// Differs from what PBKDF2 would give for the same pw/salt (different KDF).
|
||||
assert.notEqual(T.bytesToHex(new Uint8Array(raw)), refKeyHex(pwd, saltHex, 100000));
|
||||
|
||||
// argon2id-v2 is a '-v2' scheme → verifier is decoupled SHA-256, not the key.
|
||||
assert.equal(verifier, refSha256Hex(expectKey + T.AUTH_VERIFIER_DOMAIN));
|
||||
assert.notEqual(verifier, expectKey, 'argon2id-v2 verifier must not leak the key');
|
||||
});
|
||||
|
||||
test('deriveKeyAndVerifier: argon2id uses ARGON2_DEFAULT_PARAMS when none passed', async () => {
|
||||
// OWASP baseline is the default (m=19456, t=2, p=1). Just assert the
|
||||
// default object is what we expect; deriving at 19 MiB is left to one
|
||||
// explicit round-trip below to keep the suite fast.
|
||||
assert.deepEqual({ ...T.ARGON2_DEFAULT_PARAMS }, { m: 19456, t: 2, p: 1 });
|
||||
});
|
||||
|
||||
test('encrypt/decrypt round-trips under an Argon2id-derived key', async () => {
|
||||
const { cryptoKey } = await T.deriveKeyAndVerifier(
|
||||
'master', 'saltsaltsalt', 0, T.HASH_ALGO_ARGON2, { m: 512, t: 1, p: 1 });
|
||||
T.state.cryptoKey = cryptoKey;
|
||||
const { encrypted, iv } = await T.encryptPwd('argon-secret 🔐');
|
||||
assert.equal(await T.decryptPwd(encrypted, iv), 'argon-secret 🔐');
|
||||
});
|
||||
|
||||
test('verifierFromKeyHex: pure mapping matches deriveKeyAndVerifier', async () => {
|
||||
const keyHex = 'ab'.repeat(32);
|
||||
assert.equal(await T.verifierFromKeyHex(keyHex, 'anything-legacy'), keyHex);
|
||||
|
||||
+9
-1
@@ -113,6 +113,12 @@ function loadApp(overrides = {}) {
|
||||
|
||||
vm.createContext(sandbox);
|
||||
|
||||
// Load the vendored Argon2 bundle first (index.html loads it before
|
||||
// app.js). It assigns globalThis.NobleArgon2 — needed by the argon2id
|
||||
// KDF branch in deriveKeyBytes.
|
||||
const ARGON2_JS = path.join(__dirname, '..', 'argon2.js');
|
||||
vm.runInContext(fs.readFileSync(ARGON2_JS, 'utf8'), sandbox, { filename: 'argon2.js' });
|
||||
|
||||
let src = fs.readFileSync(APP_JS, 'utf8');
|
||||
|
||||
// Export epilogue — surface the lexical (const) symbols we test, plus a
|
||||
@@ -124,8 +130,10 @@ function loadApp(overrides = {}) {
|
||||
// crypto
|
||||
bytesToHex, hexToBytes: (typeof hexToBytes !== 'undefined' ? hexToBytes : undefined),
|
||||
verifierFromKeyHex, deriveKeyAndVerifier, computeVerifier,
|
||||
deriveKeyBytes, isDecoupledVerifierAlgo,
|
||||
encryptPwd, decryptPwd, sha256Hex,
|
||||
HASH_ALGO_V2, AUTH_VERIFIER_DOMAIN,
|
||||
HASH_ALGO_V2, HASH_ALGO_ARGON2, AUTH_VERIFIER_DOMAIN, ARGON2_DEFAULT_PARAMS,
|
||||
NobleArgon2: (typeof NobleArgon2 !== 'undefined' ? NobleArgon2 : undefined),
|
||||
// csv
|
||||
parseCSV, findColumn, parseEntriesFromCSV,
|
||||
// strength
|
||||
|
||||
Reference in New Issue
Block a user