'use strict'; const assert = require('node:assert/strict'); const test = require('node:test'); const { enroll, silentUnlock, recover, encryptSecret, decryptSecret, randomBytes, } = require('../src/vault'); const PASSWORD = 'correct horse battery staple'; const AAD = 'fp:abc123|sess:xyz789'; test('enroll → silent unlock works with correct AAD', async () => { const deviceKey = randomBytes(32); const { serverWrapped, deviceWrapped, salt } = await enroll({ password: PASSWORD, deviceKey, deviceAad: AAD, }); const vk = await silentUnlock(deviceKey, deviceWrapped, AAD); assert.equal(vk.length, 32); assert.ok(serverWrapped.iv && serverWrapped.data); assert.ok(salt); }); test('silent unlock fails if session/fingerprint AAD changes', async () => { const deviceKey = randomBytes(32); const { deviceWrapped } = await enroll({ password: PASSWORD, deviceKey, deviceAad: AAD }); await assert.rejects( silentUnlock(deviceKey, deviceWrapped, 'fp:abc123|sess:DIFFERENT'), /decrypt|operation/i, ); }); test('silent unlock fails with a different device key (copied blob)', async () => { const deviceKeyA = randomBytes(32); const deviceKeyB = randomBytes(32); const { deviceWrapped } = await enroll({ password: PASSWORD, deviceKey: deviceKeyA, deviceAad: AAD }); await assert.rejects(silentUnlock(deviceKeyB, deviceWrapped, AAD), /decrypt|operation/i); }); test('recover with correct password works (new device)', async () => { const { serverWrapped, salt } = await enroll({ password: PASSWORD }); const vk = await recover(PASSWORD, salt, serverWrapped); assert.equal(vk.length, 32); }); test('recover with wrong password fails', async () => { const { serverWrapped, salt } = await enroll({ password: PASSWORD }); await assert.rejects(recover('wrong password', salt, serverWrapped), /decrypt|operation/i); }); test('account secret round-trips, and fails with wrong account AAD', async () => { const { serverWrapped, salt } = await enroll({ password: PASSWORD }); const vk = await recover(PASSWORD, salt, serverWrapped); const wrapped = await encryptSecret(vk, 'sk_live_secret_key_123', 'acct:twitch-1'); const secret = await decryptSecret(vk, wrapped, 'acct:twitch-1'); assert.equal(secret, 'sk_live_secret_key_123'); // The same ciphertext can't be re-attributed to a different account. await assert.rejects(decryptSecret(vk, wrapped, 'acct:kick-2'), /decrypt|operation/i); }); test('server never sees plaintext: wrapped blobs contain no secret bytes', async () => { const deviceKey = randomBytes(32); const secret = 'sk_topsecret'; const { serverWrapped } = await enroll({ password: PASSWORD, deviceKey, deviceAad: AAD }); const vk = await silentUnlock(deviceKey, (await enroll({ password: PASSWORD, deviceKey, deviceAad: AAD })).deviceWrapped, AAD); const wrappedSecret = await encryptSecret(vk, secret, 'acct:1'); const allBlobs = JSON.stringify({ serverWrapped, wrappedSecret }); assert.ok(!allBlobs.includes(secret), 'ciphertext must not leak the plaintext secret'); });