mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
65 lines
3.0 KiB
JavaScript
65 lines
3.0 KiB
JavaScript
import { webcrypto } from 'node:crypto';
|
|
import { Buffer } from 'node:buffer';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import {
|
|
clearChatKeyCache,
|
|
countCodePoints,
|
|
decryptChatMessage,
|
|
deriveChatKey,
|
|
encryptChatMessage,
|
|
generateChatSecret,
|
|
normalizeOutgoingChatText,
|
|
validateChatSecret
|
|
} from './chat-crypto.js';
|
|
|
|
afterEach(clearChatKeyCache);
|
|
|
|
describe('chat crypto', () => {
|
|
it('generates canonical 128-bit secrets', () => {
|
|
const secret = generateChatSecret(webcrypto);
|
|
expect(secret).toMatch(/^[A-Za-z0-9_-]{22}$/);
|
|
expect(validateChatSecret(secret)).toBe(secret);
|
|
expect(validateChatSecret(`${secret}=`)).toBe('');
|
|
});
|
|
|
|
it('round-trips Unicode with a cached room key and fresh IVs', async () => {
|
|
const secret = generateChatSecret(webcrypto);
|
|
const firstKey = await deriveChatKey('ROOM-1', secret, webcrypto);
|
|
const secondKey = await deriveChatKey('ROOM-1', secret, webcrypto);
|
|
expect(secondKey).toBe(firstKey);
|
|
|
|
const input = { text: 'Hello 🐨 **world**', roomId: 'ROOM-1', senderId: 'alice', secret };
|
|
const first = await encryptChatMessage(input, webcrypto);
|
|
const second = await encryptChatMessage(input, webcrypto);
|
|
expect(second).not.toBe(first);
|
|
await expect(decryptChatMessage({ ciphertext: first, ...input }, webcrypto)).resolves.toBe(input.text);
|
|
});
|
|
|
|
it('rejects relabeling, cross-room replay, and the wrong secret', async () => {
|
|
const secret = generateChatSecret(webcrypto);
|
|
const ciphertext = await encryptChatMessage({ text: 'secret', roomId: 'ROOM-1', senderId: 'alice', secret }, webcrypto);
|
|
clearChatKeyCache();
|
|
await expect(decryptChatMessage({ ciphertext, roomId: 'ROOM-1', senderId: 'bob', secret }, webcrypto)).rejects.toThrow();
|
|
clearChatKeyCache();
|
|
await expect(decryptChatMessage({ ciphertext, roomId: 'ROOM-2', senderId: 'alice', secret }, webcrypto)).rejects.toThrow();
|
|
clearChatKeyCache();
|
|
await expect(decryptChatMessage({ ciphertext, roomId: 'ROOM-1', senderId: 'alice', secret: generateChatSecret(webcrypto) }, webcrypto)).rejects.toThrow();
|
|
});
|
|
|
|
it('enforces 500 Unicode code points before encryption', () => {
|
|
expect(countCodePoints('😀'.repeat(500))).toBe(500);
|
|
expect(normalizeOutgoingChatText(` ${'😀'.repeat(500)} `)).toBe('😀'.repeat(500));
|
|
expect(() => normalizeOutgoingChatText('😀'.repeat(501))).toThrow(RangeError);
|
|
expect(() => normalizeOutgoingChatText(' ')).toThrow(TypeError);
|
|
});
|
|
|
|
it('keeps the worst-case 500-codepoint payload within the relay byte bound', async () => {
|
|
const secret = generateChatSecret(webcrypto);
|
|
const ciphertext = await encryptChatMessage({
|
|
text: '😀'.repeat(500), roomId: 'ROOM-1', senderId: 'alice', secret
|
|
}, webcrypto);
|
|
const bytes = Buffer.from(ciphertext, 'base64url');
|
|
expect(bytes).toHaveLength(2028);
|
|
});
|
|
});
|