123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- /* eslint-disable no-bitwise */
- import { Context } from './Context';
- import { ratchet, importKey } from './crypto-utils';
-
- /*
- function hexdump(buffer) {
- const a = new Uint8Array(buffer);
- let s = '';
-
- for (let i = 0; i < a.byteLength; i++) {
- s += '0x';
- s += a[i].toString(16);
- s += ' ';
- }
-
- return s.trim();
- }
- */
-
- /* TODO: more tests
- * - delta frames
- * - frame header is not encrypted
- * - different sendCounts
- * - different key length
- * - ratcheting in decodeFunction
- * etc
- */
- const audioBytes = [ 0xde, 0xad, 0xbe, 0xef ];
- const videoBytes = [ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef ];
-
- /**
- * generates a dummy audio frame
- */
- function makeAudioFrame() {
- return {
- data: new Uint8Array(audioBytes).buffer,
- type: undefined, // type is undefined for audio frames.
- getMetadata: () => {
- return { synchronizationSource: 123 };
- }
- };
- }
-
- /**
- * generates a dummy video frame
- */
- function makeVideoFrame() {
- return {
- data: new Uint8Array(videoBytes).buffer,
- type: 'key',
- getMetadata: () => {
- return { synchronizationSource: 321 };
- }
- };
- }
-
-
- describe('E2EE Context', () => {
- let sender;
- let sendController;
- let receiver;
- let receiveController;
- const key = new Uint8Array([
- 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]);
-
- beforeEach(() => {
- sender = new Context('sender');
- receiver = new Context('receiver');
- });
-
- describe('encode function', () => {
- beforeEach(async () => {
- await sender.setKey(key, 0);
- await receiver.setKey(key, 0);
- });
-
- it('with an audio frame', async done => {
- sendController = {
- enqueue: encodedFrame => {
- const data = new Uint8Array(encodedFrame.data);
-
- // An audio frame will have an overhead of 6 bytes with this counter and key size:
- // 4 bytes truncated signature, counter (1 byte) and 1 byte trailer.
- expect(data.byteLength).toEqual(audioBytes.length + 6);
-
- // TODO: provide test vector.
- done();
- }
- };
-
- await sender.encodeFunction(makeAudioFrame(), sendController);
- });
-
- it('with a video frame', async done => {
- sendController = {
- enqueue: encodedFrame => {
- const data = new Uint8Array(encodedFrame.data);
-
- // A video frame will have an overhead of 12 bytes with this counter and key size:
- // 10 bytes signature, counter (1 byte) and 1 byte trailer.
-
- expect(data.byteLength).toEqual(videoBytes.length + 12);
-
- // TODO: provide test vector.
- done();
- }
- };
-
- await sender.encodeFunction(makeVideoFrame(), sendController);
- });
- });
-
- describe('end-to-end test', () => {
- beforeEach(async () => {
- await sender.setKey(key, 0);
- await receiver.setKey(key, 0);
- sendController = {
- enqueue: async encodedFrame => {
- await receiver.decodeFunction(encodedFrame, receiveController);
- }
- };
- });
-
- it('with an audio frame', async done => {
- receiveController = {
- enqueue: encodedFrame => {
- const data = new Uint8Array(encodedFrame.data);
-
- expect(data.byteLength).toEqual(audioBytes.length);
- expect(Array.from(data)).toEqual(audioBytes);
- done();
- }
- };
-
- await sender.encodeFunction(makeAudioFrame(), sendController);
- });
-
- it('with a video frame', async done => {
- receiveController = {
- enqueue: encodedFrame => {
- const data = new Uint8Array(encodedFrame.data);
-
- expect(data.byteLength).toEqual(videoBytes.length);
- expect(Array.from(data)).toEqual(videoBytes);
- done();
- }
- };
-
- await sender.encodeFunction(makeVideoFrame(), sendController);
- });
-
- it('the receiver ratchets forward', async done => {
- // Ratchet the key. We reimport from the raw bytes.
- const material = await importKey(key);
-
- await sender.setKey(await ratchet(material), 0);
-
- receiveController = {
- enqueue: encodedFrame => {
- const data = new Uint8Array(encodedFrame.data);
-
- expect(data.byteLength).toEqual(audioBytes.length);
- expect(Array.from(data)).toEqual(audioBytes);
- done();
- }
- };
-
- await sender.encodeFunction(makeAudioFrame(), sendController);
- });
- });
-
- describe('E2EE Signature', () => {
- let privateKey;
- let publicKey;
-
- // Generated one-time using
- // await crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-521'}, false, ['sign', 'verify']);
- // then exported as JWK. Only use as test vectors.
- const rawPublicKey = {
- crv: 'P-521',
- ext: true,
- key_ops: [ 'verify' ], // eslint-disable-line camelcase
- kty: 'EC',
- x: 'AEs3y1FyefvjTC6JaJ1s00k5CFnESu5xIofPAmu286Y4UWyx8kB3jTHPKDO8bK81XT2_HbbN9ONm2D5TYCCxoR5r',
- y: 'AZHlLHuSEWM401dy2lo-nu100Hp1ixcYePf9sNboaZruXctvoAt_sAX6MM0NccHx4587yhWfn9NG7fCX60P5KAvA'
- };
- const rawPrivateKey = {
- crv: 'P-521',
- ext: true,
- key_ops: [ 'sign' ], // eslint-disable-line camelcase
- kty: 'EC',
- d: 'AV3aTIFuO9Zm0SXVlnujUvlvGvyPrY0pEOtX2pxD2JwPvWWoLXfTA052MHhqiii2RORe_7Ivm_PNeBwhYcO04i-K',
- x: 'AEs3y1FyefvjTC6JaJ1s00k5CFnESu5xIofPAmu286Y4UWyx8kB3jTHPKDO8bK81XT2_HbbN9ONm2D5TYCCxoR5r',
- y: 'AZHlLHuSEWM401dy2lo-nu100Hp1ixcYePf9sNboaZruXctvoAt_sAX6MM0NccHx4587yhWfn9NG7fCX60P5KAvA'
- };
-
- beforeEach(async () => {
- privateKey = await crypto.subtle.importKey('jwk', rawPrivateKey, { name: 'ECDSA',
- namedCurve: 'P-521' }, false, [ 'sign' ]);
- publicKey = await crypto.subtle.importKey('jwk', rawPublicKey, { name: 'ECDSA',
- namedCurve: 'P-521' }, false, [ 'verify' ]);
-
- await sender.setKey(key, 0);
- await receiver.setKey(key, 0);
- sender.setSignatureKey(privateKey);
- receiver.setSignatureKey(publicKey);
- });
-
- it('signs the first frame', async done => {
- sendController = {
- enqueue: encodedFrame => {
- const data = new Uint8Array(encodedFrame.data);
-
- // Check that the signature bit is set.
- expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
-
- // An audio frame will have an overhead of 6 bytes with this counter and key size:
- // 4 bytes truncated signature, counter (1 byte) and 1 byte trailer.
- // In addition to that we have the 132 bytes signature.
- expect(data.byteLength).toEqual(audioBytes.length + 6 + 132);
-
- // TODO: provide test vector for the signature.
- done();
- }
- };
- await sender.encodeFunction(makeAudioFrame(), sendController);
- });
-
- it('signs subsequent frames from different sources', async done => {
- let frameCount = 0;
-
- sendController = {
- enqueue: encodedFrame => {
- frameCount++;
- const data = new Uint8Array(encodedFrame.data);
-
- expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
-
- if (frameCount === 2) {
- done();
- }
- }
- };
-
- await sender.encodeFunction(makeAudioFrame(), sendController);
-
- const secondFrame = makeAudioFrame();
-
- secondFrame.getMetadata = () => {
- return { synchronizationSource: 456 };
- };
- await sender.encodeFunction(secondFrame, sendController);
- });
-
- it('signs subsequent key frames from the same source', async done => {
- let frameCount = 0;
-
- sendController = {
- enqueue: encodedFrame => {
- frameCount++;
- const data = new Uint8Array(encodedFrame.data);
-
- expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
-
- if (frameCount === 2) {
- done();
- }
- }
- };
-
- await sender.encodeFunction(makeVideoFrame(), sendController);
- await sender.encodeFunction(makeVideoFrame(), sendController);
- });
-
-
- it('signs subsequent frames from the same source', async done => {
- let frameCount = 0;
-
- sendController = {
- enqueue: encodedFrame => {
- frameCount++;
- const data = new Uint8Array(encodedFrame.data);
-
- expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
-
- if (frameCount === 2) {
- done();
- }
- }
- };
-
- await sender.encodeFunction(makeAudioFrame(), sendController);
- await sender.encodeFunction(makeAudioFrame(), sendController);
- });
-
- it('signs after ratcheting the sender key', async done => {
- let frameCount = 0;
-
- sendController = {
- enqueue: encodedFrame => {
- frameCount++;
- const data = new Uint8Array(encodedFrame.data);
-
- expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
-
- if (frameCount === 2) {
- done();
- }
- }
- };
-
- await sender.encodeFunction(makeAudioFrame(), sendController);
-
- // Ratchet the key. We reimport from the raw bytes.
- const material = await importKey(key);
-
- await sender.setKey(await ratchet(material), 0);
- await sender.encodeFunction(makeAudioFrame(), sendController);
- });
-
- it('verifies the frame', async done => {
- sendController = {
- enqueue: async encodedFrame => {
- await receiver.decodeFunction(encodedFrame, receiveController);
- }
- };
- receiveController = {
- enqueue: encodedFrame => {
- const data = new Uint8Array(encodedFrame.data);
-
- expect(data.byteLength).toEqual(audioBytes.length);
- expect(Array.from(data)).toEqual(audioBytes);
- done();
- }
- };
- await sender.encodeFunction(makeAudioFrame(), sendController);
- });
- });
- });
|