Changes: Propose the pinned XMPP library decision and add an isolated browser-built selection spike combining reviewed public SASL, SHA-512 and parser seams. Exercise TLS/xmpp handshake, strongest compatible non-PLUS authentication, rejected unsafe paths and extension mappings; document duplicate-body export projection and remaining limits. Preserve dependency notices, evidence and QA handoff. Validation: Clean-cache pinned npm ci succeeded; npm test passed 112 checks; check-dependencies.py verified 51 locked entries, 26 installed packages and 340 unchanged vendor files; make check passed 72 tooling tests. No actual browser runtime, independent full XMPP server/client or production encryption/release readiness claim. Task: S00-003 Agent: auto-protocol-f474114f0168 Role: protocol Action: claim Status: review Task title: Проверить XMPP WSS/auth библиотеку
84 lines
4.6 KiB
JavaScript
84 lines
4.6 KiB
JavaScript
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)}));
|
|
});
|