You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Context.spec.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. /* eslint-disable no-bitwise */
  2. import { Context } from './Context';
  3. import { ratchet, importKey } from './crypto-utils';
  4. /*
  5. function hexdump(buffer) {
  6. const a = new Uint8Array(buffer);
  7. let s = '';
  8. for (let i = 0; i < a.byteLength; i++) {
  9. s += '0x';
  10. s += a[i].toString(16);
  11. s += ' ';
  12. }
  13. return s.trim();
  14. }
  15. */
  16. /* TODO: more tests
  17. * - delta frames
  18. * - frame header is not encrypted
  19. * - different sendCounts
  20. * - different key length
  21. * - ratcheting in decodeFunction
  22. * etc
  23. */
  24. const audioBytes = [ 0xde, 0xad, 0xbe, 0xef ];
  25. const videoBytes = [ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef ];
  26. /**
  27. * generates a dummy audio frame
  28. */
  29. function makeAudioFrame() {
  30. return {
  31. data: new Uint8Array(audioBytes).buffer,
  32. type: undefined, // type is undefined for audio frames.
  33. getMetadata: () => {
  34. return { synchronizationSource: 123 };
  35. }
  36. };
  37. }
  38. /**
  39. * generates a dummy video frame
  40. */
  41. function makeVideoFrame() {
  42. return {
  43. data: new Uint8Array(videoBytes).buffer,
  44. type: 'key',
  45. getMetadata: () => {
  46. return { synchronizationSource: 321 };
  47. }
  48. };
  49. }
  50. describe('E2EE Context', () => {
  51. let sender;
  52. let sendController;
  53. let receiver;
  54. let receiveController;
  55. const key = new Uint8Array([
  56. 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  57. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  58. ]);
  59. beforeEach(() => {
  60. sender = new Context('sender');
  61. receiver = new Context('receiver');
  62. });
  63. describe('encode function', () => {
  64. beforeEach(async () => {
  65. await sender.setKey(key, 0);
  66. await receiver.setKey(key, 0);
  67. });
  68. it('with an audio frame', async done => {
  69. sendController = {
  70. enqueue: encodedFrame => {
  71. const data = new Uint8Array(encodedFrame.data);
  72. // An audio frame will have an overhead of 6 bytes with this counter and key size:
  73. // 4 bytes truncated signature, counter (1 byte) and 1 byte trailer.
  74. expect(data.byteLength).toEqual(audioBytes.length + 6);
  75. // TODO: provide test vector.
  76. done();
  77. }
  78. };
  79. await sender.encodeFunction(makeAudioFrame(), sendController);
  80. });
  81. it('with a video frame', async done => {
  82. sendController = {
  83. enqueue: encodedFrame => {
  84. const data = new Uint8Array(encodedFrame.data);
  85. // A video frame will have an overhead of 12 bytes with this counter and key size:
  86. // 10 bytes signature, counter (1 byte) and 1 byte trailer.
  87. expect(data.byteLength).toEqual(videoBytes.length + 12);
  88. // TODO: provide test vector.
  89. done();
  90. }
  91. };
  92. await sender.encodeFunction(makeVideoFrame(), sendController);
  93. });
  94. });
  95. describe('end-to-end test', () => {
  96. beforeEach(async () => {
  97. await sender.setKey(key, 0);
  98. await receiver.setKey(key, 0);
  99. sendController = {
  100. enqueue: async encodedFrame => {
  101. await receiver.decodeFunction(encodedFrame, receiveController);
  102. }
  103. };
  104. });
  105. it('with an audio frame', async done => {
  106. receiveController = {
  107. enqueue: encodedFrame => {
  108. const data = new Uint8Array(encodedFrame.data);
  109. expect(data.byteLength).toEqual(audioBytes.length);
  110. expect(Array.from(data)).toEqual(audioBytes);
  111. done();
  112. }
  113. };
  114. await sender.encodeFunction(makeAudioFrame(), sendController);
  115. });
  116. it('with a video frame', async done => {
  117. receiveController = {
  118. enqueue: encodedFrame => {
  119. const data = new Uint8Array(encodedFrame.data);
  120. expect(data.byteLength).toEqual(videoBytes.length);
  121. expect(Array.from(data)).toEqual(videoBytes);
  122. done();
  123. }
  124. };
  125. await sender.encodeFunction(makeVideoFrame(), sendController);
  126. });
  127. it('the receiver ratchets forward', async done => {
  128. // Ratchet the key. We reimport from the raw bytes.
  129. const material = await importKey(key);
  130. await sender.setKey(await ratchet(material), 0);
  131. receiveController = {
  132. enqueue: encodedFrame => {
  133. const data = new Uint8Array(encodedFrame.data);
  134. expect(data.byteLength).toEqual(audioBytes.length);
  135. expect(Array.from(data)).toEqual(audioBytes);
  136. done();
  137. }
  138. };
  139. await sender.encodeFunction(makeAudioFrame(), sendController);
  140. });
  141. });
  142. describe('E2EE Signature', () => {
  143. let privateKey;
  144. let publicKey;
  145. // Generated one-time using
  146. // await crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-521'}, false, ['sign', 'verify']);
  147. // then exported as JWK. Only use as test vectors.
  148. const rawPublicKey = {
  149. crv: 'P-521',
  150. ext: true,
  151. key_ops: [ 'verify' ], // eslint-disable-line camelcase
  152. kty: 'EC',
  153. x: 'AEs3y1FyefvjTC6JaJ1s00k5CFnESu5xIofPAmu286Y4UWyx8kB3jTHPKDO8bK81XT2_HbbN9ONm2D5TYCCxoR5r',
  154. y: 'AZHlLHuSEWM401dy2lo-nu100Hp1ixcYePf9sNboaZruXctvoAt_sAX6MM0NccHx4587yhWfn9NG7fCX60P5KAvA'
  155. };
  156. const rawPrivateKey = {
  157. crv: 'P-521',
  158. ext: true,
  159. key_ops: [ 'sign' ], // eslint-disable-line camelcase
  160. kty: 'EC',
  161. d: 'AV3aTIFuO9Zm0SXVlnujUvlvGvyPrY0pEOtX2pxD2JwPvWWoLXfTA052MHhqiii2RORe_7Ivm_PNeBwhYcO04i-K',
  162. x: 'AEs3y1FyefvjTC6JaJ1s00k5CFnESu5xIofPAmu286Y4UWyx8kB3jTHPKDO8bK81XT2_HbbN9ONm2D5TYCCxoR5r',
  163. y: 'AZHlLHuSEWM401dy2lo-nu100Hp1ixcYePf9sNboaZruXctvoAt_sAX6MM0NccHx4587yhWfn9NG7fCX60P5KAvA'
  164. };
  165. beforeEach(async () => {
  166. privateKey = await crypto.subtle.importKey('jwk', rawPrivateKey, { name: 'ECDSA',
  167. namedCurve: 'P-521' }, false, [ 'sign' ]);
  168. publicKey = await crypto.subtle.importKey('jwk', rawPublicKey, { name: 'ECDSA',
  169. namedCurve: 'P-521' }, false, [ 'verify' ]);
  170. await sender.setKey(key, 0);
  171. await receiver.setKey(key, 0);
  172. sender.setSignatureKey(privateKey);
  173. receiver.setSignatureKey(publicKey);
  174. });
  175. it('signs the first frame', async done => {
  176. sendController = {
  177. enqueue: encodedFrame => {
  178. const data = new Uint8Array(encodedFrame.data);
  179. // Check that the signature bit is set.
  180. expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
  181. // An audio frame will have an overhead of 6 bytes with this counter and key size:
  182. // 4 bytes truncated signature, counter (1 byte) and 1 byte trailer.
  183. // In addition to that we have the 132 bytes signature.
  184. expect(data.byteLength).toEqual(audioBytes.length + 6 + 132);
  185. // TODO: provide test vector for the signature.
  186. done();
  187. }
  188. };
  189. await sender.encodeFunction(makeAudioFrame(), sendController);
  190. });
  191. it('signs subsequent frames from different sources', async done => {
  192. let frameCount = 0;
  193. sendController = {
  194. enqueue: encodedFrame => {
  195. frameCount++;
  196. const data = new Uint8Array(encodedFrame.data);
  197. expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
  198. if (frameCount === 2) {
  199. done();
  200. }
  201. }
  202. };
  203. await sender.encodeFunction(makeAudioFrame(), sendController);
  204. const secondFrame = makeAudioFrame();
  205. secondFrame.getMetadata = () => {
  206. return { synchronizationSource: 456 };
  207. };
  208. await sender.encodeFunction(secondFrame, sendController);
  209. });
  210. it('signs subsequent key frames from the same source', async done => {
  211. let frameCount = 0;
  212. sendController = {
  213. enqueue: encodedFrame => {
  214. frameCount++;
  215. const data = new Uint8Array(encodedFrame.data);
  216. expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
  217. if (frameCount === 2) {
  218. done();
  219. }
  220. }
  221. };
  222. await sender.encodeFunction(makeVideoFrame(), sendController);
  223. await sender.encodeFunction(makeVideoFrame(), sendController);
  224. });
  225. it('signs subsequent frames from the same source', async done => {
  226. let frameCount = 0;
  227. sendController = {
  228. enqueue: encodedFrame => {
  229. frameCount++;
  230. const data = new Uint8Array(encodedFrame.data);
  231. expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
  232. if (frameCount === 2) {
  233. done();
  234. }
  235. }
  236. };
  237. await sender.encodeFunction(makeAudioFrame(), sendController);
  238. await sender.encodeFunction(makeAudioFrame(), sendController);
  239. });
  240. it('signs after ratcheting the sender key', async done => {
  241. let frameCount = 0;
  242. sendController = {
  243. enqueue: encodedFrame => {
  244. frameCount++;
  245. const data = new Uint8Array(encodedFrame.data);
  246. expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
  247. if (frameCount === 2) {
  248. done();
  249. }
  250. }
  251. };
  252. await sender.encodeFunction(makeAudioFrame(), sendController);
  253. // Ratchet the key. We reimport from the raw bytes.
  254. const material = await importKey(key);
  255. await sender.setKey(await ratchet(material), 0);
  256. await sender.encodeFunction(makeAudioFrame(), sendController);
  257. });
  258. it('verifies the frame', async done => {
  259. sendController = {
  260. enqueue: async encodedFrame => {
  261. await receiver.decodeFunction(encodedFrame, receiveController);
  262. }
  263. };
  264. receiveController = {
  265. enqueue: encodedFrame => {
  266. const data = new Uint8Array(encodedFrame.data);
  267. expect(data.byteLength).toEqual(audioBytes.length);
  268. expect(Array.from(data)).toEqual(audioBytes);
  269. done();
  270. }
  271. };
  272. await sender.encodeFunction(makeAudioFrame(), sendController);
  273. });
  274. });
  275. });