slopus--happy
98e40dac97
CLI Smoke Test / smoke-test-linux (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-linux (24) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (24) (push) Has been cancelled
Expo App TypeScript typecheck / typecheck (push) Has been cancelled
74 行
2.9 KiB
TypeScript
74 行
2.9 KiB
TypeScript
/**
|
|
* Round-trip tests for aes.web.ts running against the same crypto.subtle
|
|
* that web-secure-encryption uses on the web build, so a successful round
|
|
* trip here implies wire compatibility with what rn-encryption emits on
|
|
* the native side (which uses the AES.GCM SealedBox combined-format:
|
|
* 12-byte nonce + ciphertext + 16-byte tag).
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
encryptAESGCMString,
|
|
decryptAESGCMString,
|
|
encryptAESGCM,
|
|
decryptAESGCM,
|
|
} from './aes.web';
|
|
import { encodeBase64 } from './base64';
|
|
|
|
function randomKeyB64(): string {
|
|
const bytes = new Uint8Array(32);
|
|
crypto.getRandomValues(bytes);
|
|
return encodeBase64(bytes);
|
|
}
|
|
|
|
describe('aes.web', () => {
|
|
it('round-trips a string', async () => {
|
|
const key = randomKeyB64();
|
|
const plain = JSON.stringify({ msg: 'Hello, World!', n: 42 });
|
|
const encrypted = await encryptAESGCMString(plain, key);
|
|
expect(typeof encrypted).toBe('string');
|
|
const decrypted = await decryptAESGCMString(encrypted, key);
|
|
expect(decrypted).toBe(plain);
|
|
});
|
|
|
|
it('produces a fresh IV per call (no two ciphertexts equal)', async () => {
|
|
const key = randomKeyB64();
|
|
const a = await encryptAESGCMString('same', key);
|
|
const b = await encryptAESGCMString('same', key);
|
|
expect(a).not.toBe(b);
|
|
});
|
|
|
|
it('rejects ciphertext encrypted under a different key', async () => {
|
|
const k1 = randomKeyB64();
|
|
const k2 = randomKeyB64();
|
|
const encrypted = await encryptAESGCMString('secret', k1);
|
|
const result = await decryptAESGCMString(encrypted, k2);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('rejects truncated ciphertext gracefully', async () => {
|
|
const key = randomKeyB64();
|
|
const encrypted = await encryptAESGCMString('hello', key);
|
|
const result = await decryptAESGCMString(encrypted.slice(0, 4), key);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('round-trips a Uint8Array via the bytes API', async () => {
|
|
const key = randomKeyB64();
|
|
const data = new TextEncoder().encode('Hello, World!');
|
|
const encrypted = await encryptAESGCM(data, key);
|
|
expect(encrypted).toBeInstanceOf(Uint8Array);
|
|
const decrypted = await decryptAESGCM(encrypted, key);
|
|
expect(decrypted).toBeInstanceOf(Uint8Array);
|
|
expect(new TextDecoder().decode(decrypted!)).toBe('Hello, World!');
|
|
});
|
|
|
|
it('produces wire format: 12-byte IV prefix + ciphertext + 16-byte tag', async () => {
|
|
const key = randomKeyB64();
|
|
const encrypted = await encryptAESGCMString('a', key);
|
|
// base64 payload = IV(12) + ciphertext("a" → 1 byte) + GCM tag(16) = 29 bytes
|
|
// base64 encoded length for 29 bytes = ceil(29/3)*4 = 40 chars (with padding)
|
|
const decoded = Uint8Array.from(atob(encrypted), (c) => c.charCodeAt(0));
|
|
expect(decoded.length).toBe(12 + 1 + 16);
|
|
});
|
|
});
|