import assert from 'node:assert/strict'; import test from 'node:test'; import {readFileSync} from 'node:fs'; import {createHash as oracleHash, createHmac as oracleHmac} from 'node:crypto'; import {Buffer as BrowserBuffer, createHash, createHmac, backend, StockScram, Sha512Mechanism, randomBytes} from '../dist/browser.mjs'; const hmacVectors = JSON.parse(readFileSync(new URL('./hmac-vectors.json', import.meta.url))); const scramVectors = JSON.parse(readFileSync(new URL('./scramp-vectors.json', import.meta.url))); test('bundle resolves browser backend and contains no external runtime imports', () => { assert.equal(backend, 'browser'); const modules = JSON.parse(readFileSync(new URL('../dist/modules.json', import.meta.url))); assert.ok(modules.some(id => id.endsWith('/platform/browser/crypto/Hmac.js'))); assert.ok(modules.some(id => id.endsWith('/platform/browser/crypto/SHA-512.js'))); assert.ok(!modules.some(id => /platform\/node\/|^node:|browser-external/.test(id))); const bundle = readFileSync(new URL('../dist/browser.mjs', import.meta.url), 'utf8'); assert.ok(!/^import\s|\brequire\(/m.test(bundle)); }); for (const vector of hmacVectors) { for (const algorithm of ['sha512', 'SHA512']) { test(`RFC 4231 case ${vector.case}, browser ${algorithm}`, () => { const key = BrowserBuffer.from(vector.key, 'hex'); const data = BrowserBuffer.from(vector.data, 'hex'); const actual = createHmac(algorithm, key).update(data).digest('hex'); assert.equal(actual.slice(0, vector.digest.length), vector.digest); }); } } test('reproduce browser hyphenated HMAC mismatch, keep Node independent control', () => { const vector = hmacVectors[0]; const key = BrowserBuffer.from(vector.key, 'hex'); const data = BrowserBuffer.from(vector.data, 'hex'); const broken = createHmac('sha-512', key).update(data).digest('hex'); assert.notEqual(broken, vector.digest); assert.equal(broken, '9656975ee5de55e75f2976ecce9a04501060b9dc22a6eda2eaef638966280182477fe09f080b2bf564649cad42af8607a2bd8d02979df3a980f15e2326a0a22a'); assert.equal(oracleHmac('sha512', Buffer.from(vector.key, 'hex')).update(Buffer.from(vector.data, 'hex')).digest('hex'), vector.digest); }); const abc = 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192' + '992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f'; for (const algorithm of ['sha512', 'sha-512', 'SHA512', 'SHA-512']) { test(`fixed SHA512 abc digest, browser ${algorithm}`, () => { assert.equal(createHash(algorithm).update('abc').digest('hex'), abc); }); } for (const length of [0, 1, 63, 64, 65, 127, 128, 129, 131, 256]) { test(`browser HMAC alias key length ${length} agrees with Node crypto`, () => { const key = Buffer.alloc(length, 0xab); const data = Buffer.alloc(257, 0xcd); assert.equal(createHmac('sha512', BrowserBuffer.from(key)).update(BrowserBuffer.from(data)).digest('hex'), oracleHmac('sha512', key).update(data).digest('hex')); assert.equal(createHash('sha512').update(BrowserBuffer.from(data)).digest('hex'), oracleHash('sha512').update(data).digest('hex')); }); } test('stock browser SHA512 proof mismatch; public alias delegate matches independent vector', () => { const vector = scramVectors[0]; const credentials = {username: vector.username, password: vector.password, clientNonce: vector.nonce}; const stock = new StockScram('SCRAM-SHA-512'); stock.createResponse(credentials); stock.processChallenge(BrowserBuffer.from(vector.first)); assert.notEqual(stock.createResponse(credentials).toString(), vector.proof); const selected = new Sha512Mechanism('SCRAM-SHA-512'); assert.equal(selected.name, vector.mechanism); assert.equal(selected.createResponse(credentials).toString(), vector.initial); selected.processChallenge(BrowserBuffer.from(vector.first)); assert.equal(selected.createResponse(credentials).toString(), vector.proof); selected.processSuccess(BrowserBuffer.from(vector.final)); assert.deepEqual(selected.finalize(), {authenticated: true, mutuallyAuthenticated: true}); assert.equal(selected.getCacheableCredentials(), null); }); test('native browser RNG path works without fixed nonce, no invented channel binding', () => { assert.equal(randomBytes(32).length, 32); const selected = new Sha512Mechanism('SCRAM-SHA-512'); const initial = selected.createResponse({username: 'user', password: 'pencil'}).toString(); assert.match(initial, /^n,,n=user,r=[0-9a-f]{64}$/); assert.throws(() => new Sha512Mechanism('SCRAM-SHA-512-PLUS')); assert.throws(() => new Sha512Mechanism('SCRAM-SHA-512').createResponse({tlsUnique: BrowserBuffer.alloc(32)})); });