Skip to content

Commit

Permalink
Increase coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmillr committed Jan 1, 2025
1 parent 67123a0 commit 7ae4db0
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 23 deletions.
15 changes: 0 additions & 15 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ export type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array |

// Cast array to different type
export const u8 = (arr: TypedArray) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
export const u16 = (arr: TypedArray) =>
new Uint16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
export const u32 = (arr: TypedArray) =>
new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));

Expand Down Expand Up @@ -86,19 +84,6 @@ export function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
// next scheduler queue processing step and this is exactly what we need.
export const nextTick = async () => {};

// Returns control to thread each 'tick' ms to avoid blocking
export async function asyncLoop(iters: number, tick: number, cb: (i: number) => void) {
let ts = Date.now();
for (let i = 0; i < iters; i++) {
cb(i);
// Date.now() is not monotonic, so in case if clock goes backwards we return return control too
const diff = Date.now() - ts;
if (diff >= 0 && diff < tick) continue;
await nextTick();
ts += diff;
}
}

// Global symbols in both browsers and Node.js since v11
// See https://github.com/microsoft/TypeScript/issues/31535
declare const TextEncoder: any;
Expand Down
37 changes: 34 additions & 3 deletions test/aes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { should, describe } = require('micro-should');
const { createCipheriv, createDecipheriv } = require('node:crypto');
const { bytesToHex, concatBytes, hexToBytes } = require('../utils.js');
const { ecb, cbc, ctr, siv, gcm, aeskw, aeskwp } = require('../aes.js');
const web = require('../webcrypto.js');
// https://datatracker.ietf.org/doc/html/rfc8452#appendix-C
const NIST_VECTORS = require('./vectors/nist_800_38a.json');
const VECTORS = require('./vectors/siv.json');
Expand Down Expand Up @@ -62,6 +63,18 @@ describe('AES', () => {
deepStrictEqual(c.decrypt(ciphertext), plaintext);
deepStrictEqual(c.encrypt(plaintext), ciphertext);
});
if (t.name === 'ctr') {
should(`${t.name}: web`, async () => {
let c;
const cipher = web.ctr;
if (t.iv) c = cipher(hex.decode(t.key), hex.decode(t.iv || ''), { disablePadding: true });
else c = cipher(hex.decode(t.key), { disablePadding: true });
const ciphertext = concatBytes(...t.blocks.map((i) => hex.decode(i.ciphertext)));
const plaintext = concatBytes(...t.blocks.map((i) => hex.decode(i.plaintext)));
deepStrictEqual(await c.decrypt(ciphertext), plaintext);
deepStrictEqual(await c.encrypt(plaintext), ciphertext);
});
}
}
});
describe('GCM-SIV', () => {
Expand All @@ -78,20 +91,25 @@ describe('AES', () => {
});
}
}
should(`throws on lengths`, () => {
siv(new Uint8Array(32), new Uint8Array(12), new Uint8Array(12));
throws(() => siv(new Uint8Array(32), new Uint8Array(11), new Uint8Array(12))); // nonce
throws(() => siv(new Uint8Array(33), new Uint8Array(12), new Uint8Array(12))); // key
});
});

describe('Wycheproof', () => {
const cases = [
{ name: 'GCM-SIV', groups: aes_gcm_siv_test.testGroups, cipher: 'siv' },
{ name: 'GCM', groups: aes_gcm_test.testGroups, cipher: 'gcm' },
{ name: 'CBC', groups: aes_cbc_test.testGroups, cipher: 'cbc' }, // PCKS5 is enabled by default
{ name: 'GCM', groups: aes_gcm_test.testGroups, cipher: 'gcm', webcipher: web.gcm },
{ name: 'CBC', groups: aes_cbc_test.testGroups, cipher: 'cbc', webcipher: web.cbc }, // PCKS5 is enabled by default
];
for (const c of cases) {
for (const g of c.groups) {
const name = `Wycheproof/${c.name}/${g.ivSize}/${g.keySize}/${g.tagSize}/${g.type}`;
for (let i = 0; i < g.tests.length; i++) {
const t = g.tests[i];
should(`${name}: ${i}`, () => {
should(`${name}: ${i}`, async () => {
const ct = concatBytes(hex.decode(t.ct), hex.decode(t.tag || ''));
const msg = hex.decode(t.msg);
const cipher = CIPHERS[c.cipher];
Expand All @@ -101,6 +119,16 @@ describe('AES', () => {
const ct = concatBytes(hex.decode(t.ct), hex.decode(t.tag || ''));
deepStrictEqual(a.decrypt(ct), msg);
deepStrictEqual(a.encrypt(msg), ct);
// Webcrypto has different limits
if (c.webcipher && t.iv.length !== 16 && t.iv.length % 16 === 0) {
const wc = c.webcipher(
hex.decode(t.key),
hex.decode(t.iv),
hex.decode(t.aad || '')
);
deepStrictEqual(await wc.decrypt(ct), msg);
deepStrictEqual(await wc.encrypt(msg), ct);
}
} else {
throws(() =>
cipher(hex.decode(t.key), hex.decode(t.iv), hex.decode(t.aad || '')).decrypt(ct)
Expand Down Expand Up @@ -180,6 +208,9 @@ describe('AES', () => {
}
}
});
should('throws on 8 byte keys', () => {
throws(() => aeskw(new Uint8Array(8)).encrypt(new Uint8Array(8)));
});
should('KWP', () => {
// https://www.rfc-editor.org/rfc/rfc5649
const vectors = [
Expand Down
35 changes: 34 additions & 1 deletion test/arx.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const { deepStrictEqual, throws } = require('assert');
const { should, describe } = require('micro-should');
const { base64 } = require('@scure/base');
const { salsa20, hsalsa, xsalsa20, xsalsa20poly1305 } = require('../salsa.js');
const { salsa20, hsalsa, xsalsa20, xsalsa20poly1305, secretbox } = require('../salsa.js');
const {
chacha20orig,
hchacha,
xchacha20,
chacha20poly1305,
xchacha20poly1305,
chacha12,
chacha20,
} = require('../chacha.js');
const { poly1305 } = require('../_poly1305.js');
const slow = require('../_micro.js');
Expand Down Expand Up @@ -105,6 +107,23 @@ describe('chacha', () => {
deepStrictEqual(hex.encode(res2), v.stream);
}
});
should('short key', () => {
const res = chacha20orig(
new Uint8Array(16).fill(1),
new Uint8Array(8).fill(2),
new Uint8Array(10).fill(10)
);
deepStrictEqual(hex.encode(res), '4ad24b21cba95a002754');
});
should('small nonce', () => {
throws(() =>
chacha20orig(
new Uint8Array(16).fill(1),
new Uint8Array(6).fill(2),
new Uint8Array(10).fill(10)
)
);
});

// test taken from draft-arciszewski-xchacha-03 section 2.2.1
// see https://tools.ietf.org/html/draft-arciszewski-xchacha-03#section-2.2.1
Expand Down Expand Up @@ -171,6 +190,14 @@ describe('chacha', () => {
deepStrictEqual(hex.encode(xchacha20(key, nonce, plaintext)), ciphertext);
deepStrictEqual(hex.encode(slow.xchacha20(key, nonce, plaintext)), ciphertext);
});
should('output length', () => {
for (const fn of [chacha12, chacha20, chacha20orig, xchacha20, xsalsa20, salsa20]) {
// thows on output < data
throws(() =>
fn(new Uint8Array(32), new Uint8Array(16), new Uint8Array(10), new Uint8Array(9))
);
}
});
});

describe('poly1305', () => {
Expand Down Expand Up @@ -239,6 +266,12 @@ should('tweetnacl secretbox compat', () => {
const cSlow = slow.xsalsa20poly1305(key, nonce);
deepStrictEqual(hex.encode(cSlow.encrypt(msg)), hex.encode(exp), i);
deepStrictEqual(hex.encode(cSlow.decrypt(exp)), hex.encode(msg), i);
// Secret box (micro)
deepStrictEqual(slow.secretbox(key, nonce).seal(msg), exp);
deepStrictEqual(slow.secretbox(key, nonce).open(exp), msg);
// Secret box
deepStrictEqual(secretbox(key, nonce).seal(msg), exp);
deepStrictEqual(secretbox(key, nonce).open(exp), msg);
}
});

Expand Down
11 changes: 9 additions & 2 deletions test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('Basic', () => {
const opts = CIPHERS[k];
should(`${k}: blockSize`, () => {
const { c, key, nonce, copy } = initCipher(opts);
const msg = new Uint8Array(opts.fn.blockSize).fill(12);
const msg = new Uint8Array(opts.blockSize).fill(12);
const msgCopy = msg.slice();
if (checkBlockSize(opts, msgCopy.length)) {
deepStrictEqual(c.decrypt(c.encrypt(msgCopy)), msg);
Expand All @@ -75,7 +75,14 @@ describe('Basic', () => {
deepStrictEqual(nonce, copy.nonce);
}
});

if (opts.blockSize) {
should(`${k}: wrong blockSize`, () => {
const { c } = initCipher(opts);
const msg = new Uint8Array(opts.blockSize - 1).fill(12);
throws(() => c.encrypt(msg));
throws(() => c.decrypt(msg));
});
}
should(`${k}: round-trip`, () => {
// slice, so cipher has no way to corrupt msg
const msg = new Uint8Array(2).fill(12);
Expand Down
5 changes: 4 additions & 1 deletion test/ff1.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { deepStrictEqual } = require('assert');
const { deepStrictEqual, throws } = require('assert');
const { describe, should } = require('micro-should');
const { FF1, BinaryFF1 } = require('../ff1.js');
const v = require('./vectors/ff1.json');
Expand Down Expand Up @@ -111,6 +111,9 @@ describe('FF1', () => {
deepStrictEqual(res, fromHex(v.data), i);
}
});
should('throw on wrong radix', () => {
throws(() => FF1(1, new Uint8Array(10)).encrypt([1]));
});
});

if (require.main === module) should.run();
126 changes: 125 additions & 1 deletion test/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ const { deepStrictEqual, throws } = require('assert');
const fc = require('fast-check');
const { describe, should } = require('micro-should');
const { TYPE_TEST, unalign } = require('./utils.js');
const { bytesToHex, concatBytes, hexToBytes, overlapBytes } = require('../utils.js');
const assert = require('../_assert.js');
const {
createView,
bytesToHex,
concatBytes,
hexToBytes,
overlapBytes,
toBytes,
bytesToUtf8,
getOutput,
setBigUint64,
u64Lengths,
} = require('../utils.js');

describe('utils', () => {
const staticHexVectors = [
Expand Down Expand Up @@ -106,6 +118,118 @@ describe('utils', () => {
deepStrictEqual(overlapBytes(main2, rightOverlap), true);
deepStrictEqual(overlapBytes(main2, inside), true);
});
should('bytesToUtf8', () => {
deepStrictEqual(bytesToUtf8(new Uint8Array([97, 98, 99])), 'abc');
});
should('toBytes', () => {
deepStrictEqual(toBytes(new Uint8Array([97, 98, 99])), new Uint8Array([97, 98, 99]));
deepStrictEqual(toBytes('abc'), new Uint8Array([97, 98, 99]));
throws(() => toBytes(1));
});
should('getOutput', () => {
deepStrictEqual(getOutput(32), new Uint8Array(32));
throws(() => getOutput(32, new Uint8Array(31)));
throws(() => getOutput(32, new Uint8Array(33)));
const t = new Uint8Array(33).subarray(1);
throws(() => getOutput(32, t));
deepStrictEqual(getOutput(32, t, false), new Uint8Array(32));
});
should('setBigUint64', () => {
const t = new Uint8Array(20);
const v = createView(t);
const VECTORS = [
{
n: 123n,
le: false,
hex: '000000000000007b000000000000000000000000',
},
{
n: 123n,
le: true,
hex: '7b00000000000000000000000000000000000000',
},
{
n: 2n ** 64n - 1n,
le: true,
hex: 'ffffffffffffffff000000000000000000000000',
},
{
n: 2n ** 64n - 1n,
le: true,
hex: '000000ffffffffffffffff000000000000000000',
pos: 3,
},
{
n: 0x123456789abcdef0n,
le: true,
hex: 'f0debc9a78563412000000000000000000000000',
},
{
n: 0x123456789abcdef0n,
le: false,
hex: '123456789abcdef0000000000000000000000000',
},
];
const createViewMock = (u8) => {
const v = createView(u8);
return {
setUint32: (o, wh, isLE) => v.setUint32(o, wh, isLE),
};
};

for (const cv of [createView, createViewMock]) {
for (const t of VECTORS) {
const b = new Uint8Array(20);
const v = cv(b);
setBigUint64(v, t.pos || 0, t.n, t.le);
deepStrictEqual(bytesToHex(b), t.hex);
}
}
});
should('u64Lengths', () => {
deepStrictEqual(bytesToHex(u64Lengths(new Uint8Array(10))), '00000000000000000a00000000000000');
deepStrictEqual(
bytesToHex(u64Lengths(new Uint8Array(10), new Uint8Array(7))),
'07000000000000000a00000000000000'
);
});
});

describe('assert', () => {
should('anumber', () => {
deepStrictEqual(assert.anumber(10), undefined);
throws(() => assert.anumber(1.2));
throws(() => assert.anumber('1'));
throws(() => assert.anumber(true));
throws(() => assert.anumber(NaN));
});
should('abytes', () => {
deepStrictEqual(assert.abytes(new Uint8Array(0)), undefined);
deepStrictEqual(assert.abytes(Buffer.alloc(10)), undefined);
deepStrictEqual(assert.abytes(new Uint8Array(10)), undefined);
assert.abytes(new Uint8Array(11), 11, 12);
assert.abytes(new Uint8Array(12), 12, 12);
throws(() => assert.abytes('test'));
throws(() => assert.abytes(new Uint8Array(10), 11, 12));
throws(() => assert.abytes(new Uint8Array(10), 11, 12));
});
should('ahash', () => {
const sha256 = () => {};
sha256.blockLen = 1;
sha256.outputLen = 1;
sha256.create = () => {};
deepStrictEqual(assert.ahash(sha256), undefined);
throws(() => assert.ahash({}));
throws(() => assert.ahash({ blockLen: 1, outputLen: 1, create: () => {} }));
});
should('aexists', () => {
deepStrictEqual(assert.aexists({}), undefined);
throws(() => assert.aexists({ destroyed: true }));
});
should('aoutput', () => {
deepStrictEqual(assert.aoutput(new Uint8Array(10), { outputLen: 5 }), undefined);
throws(() => assert.aoutput(new Uint8Array(1), { outputLen: 5 }));
});
});

describe('utils etc', () => {
Expand Down

0 comments on commit 7ae4db0

Please sign in to comment.