From d9bc8ebc538d52bb576eb8bc02a2282038a9d87c Mon Sep 17 00:00:00 2001
From: Richard van der Hoff <richard@matrix.org>
Date: Thu, 8 Jun 2017 13:12:27 +0100
Subject: [PATCH] de-lint MegolmExportEncryption + test

---
 .eslintignore.errorfiles                      |  4 --
 .../views/dialogs/ExportE2eKeysDialog.js      |  4 +-
 .../views/dialogs/ImportE2eKeysDialog.js      |  4 +-
 src/utils/MegolmExportEncryption.js           | 56 +++++++++----------
 test/.eslintrc.js                             |  7 ++-
 test/utils/MegolmExportEncryption-test.js     | 45 ++++++++++-----
 6 files changed, 69 insertions(+), 51 deletions(-)

diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles
index dedde7e07d..f1b63d7367 100644
--- a/.eslintignore.errorfiles
+++ b/.eslintignore.errorfiles
@@ -2,8 +2,6 @@
 
 src/AddThreepid.js
 src/async-components/views/dialogs/EncryptedEventDialog.js
-src/async-components/views/dialogs/ExportE2eKeysDialog.js
-src/async-components/views/dialogs/ImportE2eKeysDialog.js
 src/autocomplete/AutocompleteProvider.js
 src/autocomplete/Autocompleter.js
 src/autocomplete/Components.js
@@ -167,7 +165,6 @@ src/UserActivity.js
 src/utils/DecryptFile.js
 src/utils/DMRoomMap.js
 src/utils/FormattingUtils.js
-src/utils/MegolmExportEncryption.js
 src/utils/MultiInviter.js
 src/utils/Receipt.js
 src/Velociraptor.js
@@ -188,4 +185,3 @@ test/mock-clock.js
 test/skinned-sdk.js
 test/stores/RoomViewStore-test.js
 test/test-utils.js
-test/utils/MegolmExportEncryption-test.js
diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/ExportE2eKeysDialog.js
index d6f16a7105..045ea63c34 100644
--- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js
+++ b/src/async-components/views/dialogs/ExportE2eKeysDialog.js
@@ -120,7 +120,7 @@ export default React.createClass({
                                 'you have received in encrypted rooms to a local file. You ' +
                                 'will then be able to import the file into another Matrix ' +
                                 'client in the future, so that client will also be able to ' +
-                                'decrypt these messages.'
+                                'decrypt these messages.',
                             ) }
                         </p>
                         <p>
@@ -130,7 +130,7 @@ export default React.createClass({
                                 'careful to keep it secure. To help with this, you should enter ' +
                                 'a passphrase below, which will be used to encrypt the exported ' +
                                 'data. It will only be possible to import the data by using the ' +
-                                'same passphrase.'
+                                'same passphrase.',
                             ) }
                         </p>
                         <div className='error'>
diff --git a/src/async-components/views/dialogs/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/ImportE2eKeysDialog.js
index 61d2aeec74..91010d33b9 100644
--- a/src/async-components/views/dialogs/ImportE2eKeysDialog.js
+++ b/src/async-components/views/dialogs/ImportE2eKeysDialog.js
@@ -122,13 +122,13 @@ export default React.createClass({
                                 'This process allows you to import encryption keys ' +
                                 'that you had previously exported from another Matrix ' +
                                 'client. You will then be able to decrypt any ' +
-                                'messages that the other client could decrypt.'
+                                'messages that the other client could decrypt.',
                             ) }
                         </p>
                         <p>
                             { _t(
                                 'The export file will be protected with a passphrase. ' +
-                                'You should enter the passphrase here, to decrypt the file.'
+                                'You should enter the passphrase here, to decrypt the file.',
                             ) }
                         </p>
                         <div className='error'>
diff --git a/src/utils/MegolmExportEncryption.js b/src/utils/MegolmExportEncryption.js
index c98c467e1c..de39ea71cc 100644
--- a/src/utils/MegolmExportEncryption.js
+++ b/src/utils/MegolmExportEncryption.js
@@ -32,7 +32,7 @@ const subtleCrypto = window.crypto.subtle || window.crypto.webkitSubtle;
 /**
  * Decrypt a megolm key file
  *
- * @param {ArrayBuffer} file
+ * @param {ArrayBuffer} data file to decrypt
  * @param {String} password
  * @return {Promise<String>} promise for decrypted output
  */
@@ -61,12 +61,12 @@ export function decryptMegolmKeyFile(data, password) {
     const hmac = body.subarray(-32);
 
     return deriveKeys(salt, iterations, password).then((keys) => {
-        const [aes_key, hmac_key] = keys;
+        const [aesKey, hmacKey] = keys;
 
         const toVerify = body.subarray(0, -32);
         return subtleCrypto.verify(
             {name: 'HMAC'},
-            hmac_key,
+            hmacKey,
             hmac,
             toVerify,
         ).then((isValid) => {
@@ -80,7 +80,7 @@ export function decryptMegolmKeyFile(data, password) {
                     counter: iv,
                     length: 64,
                 },
-                aes_key,
+                aesKey,
                 ciphertext,
             );
         });
@@ -102,7 +102,7 @@ export function decryptMegolmKeyFile(data, password) {
  */
 export function encryptMegolmKeyFile(data, password, options) {
     options = options || {};
-    const kdf_rounds = options.kdf_rounds || 500000;
+    const kdfRounds = options.kdf_rounds || 500000;
 
     const salt = new Uint8Array(16);
     window.crypto.getRandomValues(salt);
@@ -115,8 +115,8 @@ export function encryptMegolmKeyFile(data, password, options) {
     // of a single bit of iv is a price we have to pay.
     iv[9] &= 0x7f;
 
-    return deriveKeys(salt, kdf_rounds, password).then((keys) => {
-        const [aes_key, hmac_key] = keys;
+    return deriveKeys(salt, kdfRounds, password).then((keys) => {
+        const [aesKey, hmacKey] = keys;
 
         return subtleCrypto.encrypt(
             {
@@ -124,7 +124,7 @@ export function encryptMegolmKeyFile(data, password, options) {
                 counter: iv,
                 length: 64,
             },
-            aes_key,
+            aesKey,
             new TextEncoder().encode(data),
         ).then((ciphertext) => {
             const cipherArray = new Uint8Array(ciphertext);
@@ -134,17 +134,17 @@ export function encryptMegolmKeyFile(data, password, options) {
             resultBuffer[idx++] = 1; // version
             resultBuffer.set(salt, idx); idx += salt.length;
             resultBuffer.set(iv, idx); idx += iv.length;
-            resultBuffer[idx++] = kdf_rounds >> 24;
-            resultBuffer[idx++] = (kdf_rounds >> 16) & 0xff;
-            resultBuffer[idx++] = (kdf_rounds >> 8) & 0xff;
-            resultBuffer[idx++] = kdf_rounds & 0xff;
+            resultBuffer[idx++] = kdfRounds >> 24;
+            resultBuffer[idx++] = (kdfRounds >> 16) & 0xff;
+            resultBuffer[idx++] = (kdfRounds >> 8) & 0xff;
+            resultBuffer[idx++] = kdfRounds & 0xff;
             resultBuffer.set(cipherArray, idx); idx += cipherArray.length;
 
             const toSign = resultBuffer.subarray(0, idx);
 
             return subtleCrypto.sign(
                 {name: 'HMAC'},
-                hmac_key,
+                hmacKey,
                 toSign,
             ).then((hmac) => {
                 hmac = new Uint8Array(hmac);
@@ -170,7 +170,7 @@ function deriveKeys(salt, iterations, password) {
         new TextEncoder().encode(password),
         {name: 'PBKDF2'},
         false,
-        ['deriveBits']
+        ['deriveBits'],
     ).then((key) => {
         return subtleCrypto.deriveBits(
             {
@@ -180,33 +180,33 @@ function deriveKeys(salt, iterations, password) {
                 hash: 'SHA-512',
             },
             key,
-            512
+            512,
         );
     }).then((keybits) => {
         const now = new Date();
         console.log("E2e import/export: deriveKeys took " + (now - start) + "ms");
 
-        const aes_key = keybits.slice(0, 32);
-        const hmac_key = keybits.slice(32);
+        const aesKey = keybits.slice(0, 32);
+        const hmacKey = keybits.slice(32);
 
-        const aes_prom = subtleCrypto.importKey(
+        const aesProm = subtleCrypto.importKey(
             'raw',
-            aes_key,
+            aesKey,
             {name: 'AES-CTR'},
             false,
-            ['encrypt', 'decrypt']
+            ['encrypt', 'decrypt'],
         );
-        const hmac_prom = subtleCrypto.importKey(
+        const hmacProm = subtleCrypto.importKey(
             'raw',
-            hmac_key,
+            hmacKey,
             {
                 name: 'HMAC',
                 hash: {name: 'SHA-256'},
             },
             false,
-            ['sign', 'verify']
+            ['sign', 'verify'],
         );
-        return Promise.all([aes_prom, hmac_prom]);
+        return Promise.all([aesProm, hmacProm]);
     });
 }
 
@@ -301,7 +301,7 @@ function packMegolmKeyFile(data) {
 function encodeBase64(uint8Array) {
     // Misinterpt the Uint8Array as Latin-1.
     // window.btoa expects a unicode string with codepoints in the range 0-255.
-    var latin1String = String.fromCharCode.apply(null, uint8Array);
+    const latin1String = String.fromCharCode.apply(null, uint8Array);
     // Use the builtin base64 encoder.
     return window.btoa(latin1String);
 }
@@ -313,10 +313,10 @@ function encodeBase64(uint8Array) {
  */
 function decodeBase64(base64) {
     // window.atob returns a unicode string with codepoints in the range 0-255.
-    var latin1String = window.atob(base64);
+    const latin1String = window.atob(base64);
     // Encode the string as a Uint8Array
-    var uint8Array = new Uint8Array(latin1String.length);
-    for (var i = 0; i < latin1String.length; i++) {
+    const uint8Array = new Uint8Array(latin1String.length);
+    for (let i = 0; i < latin1String.length; i++) {
         uint8Array[i] = latin1String.charCodeAt(i);
     }
     return uint8Array;
diff --git a/test/.eslintrc.js b/test/.eslintrc.js
index 4cc4659d7d..537f989b62 100644
--- a/test/.eslintrc.js
+++ b/test/.eslintrc.js
@@ -2,4 +2,9 @@ module.exports = {
     env: {
         mocha: true,
     },
-}
+
+    // mocha defines a 'this'
+    rules: {
+        "babel/no-invalid-this": "off",
+    },
+};
diff --git a/test/utils/MegolmExportEncryption-test.js b/test/utils/MegolmExportEncryption-test.js
index 1c90c7a030..2637097837 100644
--- a/test/utils/MegolmExportEncryption-test.js
+++ b/test/utils/MegolmExportEncryption-test.js
@@ -25,23 +25,40 @@ const TEST_VECTORS=[
     [
         "plain",
         "password",
-        "-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\ncissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----"
+        "-----BEGIN MEGOLM SESSION DATA-----\n" +
+        "AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" +
+        "cissyYBxjsfsAndErh065A8=\n" +
+        "-----END MEGOLM SESSION DATA-----",
     ],
     [
         "Hello, World",
         "betterpassword",
-        "-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\nKYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----"
+        "-----BEGIN MEGOLM SESSION DATA-----\n" +
+        "AW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\n" +
+        "KYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n" +
+        "-----END MEGOLM SESSION DATA-----",
     ],
     [
         "alphanumericallyalphanumericallyalphanumericallyalphanumerically",
         "SWORDFISH",
-        "-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\nMgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----"
+        "-----BEGIN MEGOLM SESSION DATA-----\n" +
+        "AXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\n" +
+        "MgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\n" +
+        "Pgg29363BGR+/Ripq/VCLKGNbw==\n" +
+        "-----END MEGOLM SESSION DATA-----",
     ],
     [
         "alphanumericallyalphanumericallyalphanumericallyalphanumerically",
-        "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword",
-        "-----BEGIN MEGOLM SESSION DATA-----\nAf//////////////////////////////////////////AAAD6IAZJy7IQ7Y0idqSw/bmpngEEVVh\ngsH+8ptgqxw6ZVWQnohr8JsuwH9SwGtiebZuBu5smPCO+RFVWH2cQYslZijXv/BEH/txvhUrrtCd\nbWnSXS9oymiqwUIGs08sXI33ZA==\n-----END MEGOLM SESSION DATA-----"
-    ]
+        "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" +
+        "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" +
+        "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" +
+        "passwordpasswordpasswordpasswordpassword",
+        "-----BEGIN MEGOLM SESSION DATA-----\n" +
+        "Af//////////////////////////////////////////AAAD6IAZJy7IQ7Y0idqSw/bmpngEEVVh\n" +
+        "gsH+8ptgqxw6ZVWQnohr8JsuwH9SwGtiebZuBu5smPCO+RFVWH2cQYslZijXv/BEH/txvhUrrtCd\n" +
+        "bWnSXS9oymiqwUIGs08sXI33ZA==\n" +
+        "-----END MEGOLM SESSION DATA-----",
+    ],
 ]
 ;
 
@@ -55,7 +72,7 @@ describe('MegolmExportEncryption', function() {
         if (!window.crypto.subtle && !window.crypto.webkitSubtle) {
             this.skip();
         }
-    })
+    });
 
     beforeEach(function() {
         testUtils.beforeEach(this);
@@ -64,14 +81,14 @@ describe('MegolmExportEncryption', function() {
     describe('decrypt', function() {
         it('should handle missing header', function() {
             const input=stringToArray(`-----`);
-            expect(()=>{MegolmExportEncryption.decryptMegolmKeyFile(input, '')})
+            expect(()=>MegolmExportEncryption.decryptMegolmKeyFile(input, ''))
                 .toThrow('Header line not found');
         });
 
         it('should handle missing trailer', function() {
             const input=stringToArray(`-----BEGIN MEGOLM SESSION DATA-----
 -----`);
-            expect(()=>{MegolmExportEncryption.decryptMegolmKeyFile(input, '')})
+            expect(()=>MegolmExportEncryption.decryptMegolmKeyFile(input, ''))
                 .toThrow('Trailer line not found');
         });
 
@@ -81,7 +98,7 @@ AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx
 cissyYBxjsfsAn
 -----END MEGOLM SESSION DATA-----
 `);
-            expect(()=>{MegolmExportEncryption.decryptMegolmKeyFile(input, '')})
+            expect(()=>MegolmExportEncryption.decryptMegolmKeyFile(input, ''))
                 .toThrow('Invalid file: too short');
         });
 
@@ -94,12 +111,12 @@ cissyYBxjsfsAn
 
                 const [plain, password, input] = TEST_VECTORS[i];
                 return MegolmExportEncryption.decryptMegolmKeyFile(
-                    stringToArray(input), password
+                    stringToArray(input), password,
                 ).then((decrypted) => {
                     expect(decrypted).toEqual(plain);
                     return next(i+1);
-                })
-            };
+                });
+            }
             return next(0).catch(done);
         });
     });
@@ -115,7 +132,7 @@ cissyYBxjsfsAn
                 input, password, {kdf_rounds: 1000},
             ).then((ciphertext) => {
                 return MegolmExportEncryption.decryptMegolmKeyFile(
-                    ciphertext, password
+                    ciphertext, password,
                 );
             }).then((plaintext) => {
                 expect(plaintext).toEqual(input);