test: add per-listing keypair and TOFU key-pinning tests
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import { asyncTest, describe, assertEquals, assertTrue, assertFalse, assertNotEquals } from './test-runner.js'
|
||||
import { cryptoService } from '../js/services/crypto.js'
|
||||
import { conversationsService } from '../js/services/conversations.js'
|
||||
import { keyPinningService } from '../js/services/key-pinning.js'
|
||||
|
||||
const TEST_UUID = '00000000-0000-4000-a000-000000000001'
|
||||
const TEST_UUID_2 = '00000000-0000-4000-a000-000000000002'
|
||||
@@ -250,6 +251,126 @@ describe('ConversationsService.hashPublicKey', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ── Per-listing keypair management ──
|
||||
|
||||
describe('Per-listing keypair management', () => {
|
||||
asyncTest('generateListingKeyPair creates and stores keypair', async () => {
|
||||
localStorage.removeItem('dgray_listing_keys')
|
||||
await cryptoService.unlock(TEST_UUID)
|
||||
|
||||
const publicKey = await cryptoService.generateListingKeyPair('test-listing-1')
|
||||
assertTrue(publicKey !== null)
|
||||
assertTrue(publicKey.length > 10)
|
||||
})
|
||||
|
||||
asyncTest('getListingSecretKey retrieves stored key', async () => {
|
||||
const secretKey = await cryptoService.getListingSecretKey('test-listing-1')
|
||||
assertTrue(secretKey !== null)
|
||||
assertTrue(secretKey.length > 10)
|
||||
})
|
||||
|
||||
asyncTest('getListingSecretKey returns null for unknown listing', async () => {
|
||||
const secretKey = await cryptoService.getListingSecretKey('nonexistent')
|
||||
assertEquals(secretKey, null)
|
||||
})
|
||||
|
||||
asyncTest('encrypt/decrypt with listing-specific secret key', async () => {
|
||||
await cryptoService.ready
|
||||
const nacl = cryptoService.nacl
|
||||
const naclUtil = cryptoService.naclUtil
|
||||
|
||||
// Simulate: seller creates listing key, buyer uses account key
|
||||
const buyerKp = nacl.box.keyPair()
|
||||
const buyerPub = naclUtil.encodeBase64(buyerKp.publicKey)
|
||||
|
||||
// Seller generates listing keypair
|
||||
await cryptoService.unlock(TEST_UUID)
|
||||
const sellerListingPub = await cryptoService.generateListingKeyPair('test-listing-2')
|
||||
const sellerListingSk = await cryptoService.getListingSecretKey('test-listing-2')
|
||||
|
||||
// Buyer encrypts to seller listing key
|
||||
cryptoService.keyPair = buyerKp
|
||||
const { nonce, ciphertext } = cryptoService.encrypt('Hello seller!', sellerListingPub)
|
||||
|
||||
// Seller decrypts using listing secret key (not account key)
|
||||
const plaintext = cryptoService.decrypt(ciphertext, nonce, buyerPub, sellerListingSk)
|
||||
assertEquals(plaintext, 'Hello seller!')
|
||||
})
|
||||
|
||||
asyncTest('seller can encrypt back to buyer using listing key', async () => {
|
||||
await cryptoService.ready
|
||||
const nacl = cryptoService.nacl
|
||||
const naclUtil = cryptoService.naclUtil
|
||||
|
||||
const buyerKp = nacl.box.keyPair()
|
||||
const buyerPub = naclUtil.encodeBase64(buyerKp.publicKey)
|
||||
|
||||
const sellerListingSk = await cryptoService.getListingSecretKey('test-listing-2')
|
||||
assertTrue(sellerListingSk !== null)
|
||||
|
||||
// Seller encrypts with listing key
|
||||
const { nonce, ciphertext } = cryptoService.encrypt('Hi buyer!', buyerPub, sellerListingSk)
|
||||
|
||||
// Buyer decrypts
|
||||
const sk = naclUtil.decodeBase64(sellerListingSk)
|
||||
const kp = nacl.box.keyPair.fromSecretKey(sk)
|
||||
const sellerListingPub = naclUtil.encodeBase64(kp.publicKey)
|
||||
|
||||
cryptoService.keyPair = buyerKp
|
||||
const plaintext = cryptoService.decrypt(ciphertext, nonce, sellerListingPub)
|
||||
assertEquals(plaintext, 'Hi buyer!')
|
||||
})
|
||||
|
||||
asyncTest('destroyKeyPair also clears listing keys', async () => {
|
||||
await cryptoService.unlock(TEST_UUID)
|
||||
await cryptoService.generateListingKeyPair('temp-listing')
|
||||
|
||||
cryptoService.destroyKeyPair()
|
||||
assertEquals(localStorage.getItem('dgray_listing_keys'), null)
|
||||
})
|
||||
})
|
||||
|
||||
// ── Key pinning (TOFU) ──
|
||||
|
||||
describe('KeyPinningService (TOFU)', () => {
|
||||
asyncTest('check returns "new" for unknown listing and pins it', async () => {
|
||||
keyPinningService.clear()
|
||||
const result = keyPinningService.check('listing-a', 'pubkey-123')
|
||||
assertEquals(result, 'new')
|
||||
})
|
||||
|
||||
asyncTest('check returns "ok" for matching pinned key', async () => {
|
||||
const result = keyPinningService.check('listing-a', 'pubkey-123')
|
||||
assertEquals(result, 'ok')
|
||||
})
|
||||
|
||||
asyncTest('check returns "changed" when key differs from pinned', async () => {
|
||||
const result = keyPinningService.check('listing-a', 'pubkey-different')
|
||||
assertEquals(result, 'changed')
|
||||
})
|
||||
|
||||
asyncTest('acceptChange updates pinned key', async () => {
|
||||
keyPinningService.acceptChange('listing-a', 'pubkey-different')
|
||||
const result = keyPinningService.check('listing-a', 'pubkey-different')
|
||||
assertEquals(result, 'ok')
|
||||
})
|
||||
|
||||
asyncTest('unpin removes pin for listing', async () => {
|
||||
keyPinningService.unpin('listing-a')
|
||||
const result = keyPinningService.check('listing-a', 'pubkey-new')
|
||||
assertEquals(result, 'new')
|
||||
})
|
||||
|
||||
asyncTest('clear removes all pinned keys', async () => {
|
||||
keyPinningService.check('listing-x', 'key-x')
|
||||
keyPinningService.check('listing-y', 'key-y')
|
||||
keyPinningService.clear()
|
||||
assertEquals(keyPinningService.check('listing-x', 'key-x'), 'new')
|
||||
assertEquals(keyPinningService.check('listing-y', 'key-y'), 'new')
|
||||
keyPinningService.clear()
|
||||
})
|
||||
})
|
||||
|
||||
// Cleanup after all tests
|
||||
describe('Crypto test cleanup', () => {
|
||||
asyncTest('cleanup test data from storage', async () => {
|
||||
|
||||
Reference in New Issue
Block a user