128 lines
4.2 KiB
JavaScript
128 lines
4.2 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const test = require('node:test');
|
|
const { Boss, SymmetricKey } = require('unicrypto');
|
|
const {
|
|
createCrypstie,
|
|
decryptCrypstiePayload,
|
|
encodeCrypstieKey,
|
|
initializeCrypto,
|
|
openCrypstie,
|
|
parseCrypstieUrl,
|
|
} = require('../src');
|
|
|
|
const KEY_BYTES = Uint8Array.from({ length: 32 }, (_, index) => index + 1);
|
|
const KEY_TEXT = Buffer.from(KEY_BYTES)
|
|
.toString('base64')
|
|
.replaceAll('+', '.')
|
|
.replaceAll('/', '_')
|
|
.replace(/=+$/, '');
|
|
|
|
test('parses the legacy Crypstie URL format', () => {
|
|
const parsed = parseCrypstieUrl(`https://crypstie.com/_sample#${KEY_TEXT}`);
|
|
assert.equal(parsed.id, 'sample');
|
|
assert.deepEqual(parsed.key, KEY_BYTES);
|
|
});
|
|
|
|
test('encodes and decodes legacy Crypstie keys', () => {
|
|
assert.deepEqual(parseCrypstieUrl(`https://crypstie.com/_x#${encodeCrypstieKey(KEY_BYTES)}`).key, KEY_BYTES);
|
|
});
|
|
|
|
test('rejects a link without its fragment key', () => {
|
|
assert.throws(
|
|
() => parseCrypstieUrl('https://crypstie.com/_sample'),
|
|
/no decryption key/,
|
|
);
|
|
});
|
|
|
|
test('decrypts a BOSS payload produced in the Crypstie wire format', async () => {
|
|
await initializeCrypto();
|
|
const key = new SymmetricKey({ keyBytes: KEY_BYTES });
|
|
const deleteAt = new Date('2030-01-02T03:04:05Z');
|
|
const packed = Boss.dump({
|
|
guid: 'sample',
|
|
encryptedData: key.etaEncryptSync(new TextEncoder().encode('совместимость работает')),
|
|
deleteAt,
|
|
burnOnShow: false,
|
|
showWide: true,
|
|
syntaxHighlighting: 'text',
|
|
editable: false,
|
|
ownerHandle: null,
|
|
});
|
|
|
|
const result = await decryptCrypstiePayload(packed, KEY_BYTES);
|
|
assert.equal(result.text, 'совместимость работает');
|
|
assert.equal(result.id, 'sample');
|
|
assert.equal(result.deleteAt.toISOString(), deleteAt.toISOString());
|
|
assert.equal(result.burnOnShow, false);
|
|
assert.equal(result.showWide, true);
|
|
});
|
|
|
|
test('downloads the encrypted payload without sending the fragment key', async () => {
|
|
await initializeCrypto();
|
|
const key = new SymmetricKey({ keyBytes: KEY_BYTES });
|
|
const packed = Boss.dump({
|
|
guid: 'sample',
|
|
encryptedData: key.etaEncryptSync(new TextEncoder().encode('secret')),
|
|
deleteAt: new Date('2030-01-02T03:04:05Z'),
|
|
burnOnShow: false,
|
|
showWide: false,
|
|
editable: false,
|
|
});
|
|
let requested;
|
|
const fakeFetch = async (url) => {
|
|
requested = url.href;
|
|
return new Response(packed, { status: 200 });
|
|
};
|
|
|
|
const result = await openCrypstie(
|
|
`https://crypstie.com/_sample#${KEY_TEXT}`,
|
|
{ fetch: fakeFetch },
|
|
);
|
|
|
|
assert.equal(requested, 'https://crypstie.com/api/crypstie/sample');
|
|
assert.equal(requested.includes(KEY_TEXT), false);
|
|
assert.equal(result.text, 'secret');
|
|
});
|
|
|
|
test('creates a server-compatible encrypted BOSS payload', async () => {
|
|
let request;
|
|
const fakeFetch = async (url, options) => {
|
|
request = { url: url.href, options };
|
|
return new Response('new_id', { status: 200 });
|
|
};
|
|
const deleteAt = new Date(Date.now() + 60_000);
|
|
const result = await createCrypstie('created by node', {
|
|
key: KEY_BYTES,
|
|
deleteAt,
|
|
rootUrl: 'https://example.test/base',
|
|
burnOnShow: true,
|
|
fetch: fakeFetch,
|
|
});
|
|
|
|
assert.equal(request.url, 'https://example.test/api/crypstie');
|
|
assert.equal(request.options.method, 'POST');
|
|
assert.equal(result.url, `https://example.test/_new_id#${KEY_TEXT}`);
|
|
assert.equal(result.burnOnShow, true);
|
|
|
|
const uploaded = Boss.load(new Uint8Array(await request.options.body.get('data').arrayBuffer()));
|
|
assert.equal(uploaded.burnOnShow, true);
|
|
assert.equal(
|
|
uploaded.deleteAt.toISOString(),
|
|
new Date(Math.floor(deleteAt.getTime() / 1000) * 1000).toISOString(),
|
|
);
|
|
assert.equal(
|
|
new TextDecoder().decode(new SymmetricKey({ keyBytes: KEY_BYTES }).etaDecryptSync(uploaded.encryptedData)),
|
|
'created by node',
|
|
);
|
|
});
|
|
|
|
test('reports authentication failure for a wrong key', async () => {
|
|
await initializeCrypto();
|
|
const key = new SymmetricKey({ keyBytes: KEY_BYTES });
|
|
const packed = Boss.dump({ encryptedData: key.etaEncryptSync(new TextEncoder().encode('secret')) });
|
|
const wrongKey = Uint8Array.from(KEY_BYTES, (byte) => byte ^ 0xff);
|
|
await assert.rejects(() => decryptCrypstiePayload(packed, wrongKey), /authenticate or decrypt/);
|
|
});
|