2026-07-17 13:50:30 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import test from 'node:test'
|
2026-07-20 19:31:43 +08:00
|
|
|
import { bytesToBase64, decryptXuqmFile, hmacSha256Base64Url } from '../src/crypto'
|
2026-07-17 13:50:30 +08:00
|
|
|
|
|
|
|
|
const CONFIG_VECTOR =
|
|
|
|
|
'XUQM-CONFIG-V1.AAECAwQFBgcICQoLDA0ODw.EBESExQVFhcYGRob.vhdKh3AhEdeftXCdXp9KusVySkrBNAzmdaA747uf1tVWGFnAsJeCLe2mHKk0dxxez8SRTknIbk1UuRibnXY0Gbot1fmkR-jSN2ssMJO0_zQ8dUu8'
|
|
|
|
|
|
|
|
|
|
test('hmacSha256Base64Url matches the standard HMAC-SHA256 vector', () => {
|
|
|
|
|
assert.equal(
|
|
|
|
|
hmacSha256Base64Url('key', 'The quick brown fox jumps over the lazy dog'),
|
|
|
|
|
'97yD9DBThCSxMpjmqm-xQ-9NWaFJRhdZl0edvC0aPNg',
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-20 19:31:43 +08:00
|
|
|
test('bytesToBase64 supports plugin-sized byte arrays', () => {
|
|
|
|
|
const bytes = Uint8Array.from({ length: 100_000 }, (_, index) => index % 251)
|
|
|
|
|
assert.equal(Buffer.from(bytes).toString('base64'), bytesToBase64(bytes))
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-17 13:50:30 +08:00
|
|
|
test('decryptXuqmFile remains compatible with existing PBKDF2 + AES-256-GCM files', async () => {
|
|
|
|
|
await assert.doesNotReject(async () => {
|
|
|
|
|
const result = await decryptXuqmFile(CONFIG_VECTOR)
|
|
|
|
|
assert.deepEqual(result, {
|
|
|
|
|
appKey: 'test-app',
|
|
|
|
|
appName: '兼容向量',
|
|
|
|
|
signingKey: 'secret',
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('decryptXuqmFile does not depend on a React Native TextDecoder polyfill', async () => {
|
|
|
|
|
const original = globalThis.TextDecoder
|
|
|
|
|
Object.defineProperty(globalThis, 'TextDecoder', {
|
|
|
|
|
configurable: true,
|
|
|
|
|
value: class BrokenTextDecoder {
|
|
|
|
|
decode(): string {
|
|
|
|
|
return '[object Uint8Array]'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await decryptXuqmFile(CONFIG_VECTOR)
|
|
|
|
|
assert.equal(result.appName, '兼容向量')
|
|
|
|
|
} finally {
|
|
|
|
|
Object.defineProperty(globalThis, 'TextDecoder', {
|
|
|
|
|
configurable: true,
|
|
|
|
|
value: original,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('decryptXuqmFile rejects invalid authentication tags', async () => {
|
|
|
|
|
await assert.rejects(() => decryptXuqmFile(`${CONFIG_VECTOR.slice(0, -1)}A`), /decryption failed/)
|
|
|
|
|
})
|