From 19edcd436e5290ff2f50c09b435605212c32c0c6 Mon Sep 17 00:00:00 2001
From: Hubert Chathi <hubert@uhoreg.ca>
Date: Thu, 28 May 2020 15:07:39 -0400
Subject: [PATCH 1/4] use keytar to store pickle keys

---
 hak/keytar/build.js     | 45 ++++++++++++++++++++++++++++++++
 hak/keytar/check.js     | 58 +++++++++++++++++++++++++++++++++++++++++
 hak/keytar/fetchDeps.js | 26 ++++++++++++++++++
 hak/keytar/hak.json     | 12 +++++++++
 package.json            |  3 ++-
 src/electron-main.js    | 46 ++++++++++++++++++++++++++++++++
 6 files changed, 189 insertions(+), 1 deletion(-)
 create mode 100644 hak/keytar/build.js
 create mode 100644 hak/keytar/check.js
 create mode 100644 hak/keytar/fetchDeps.js
 create mode 100644 hak/keytar/hak.json

diff --git a/hak/keytar/build.js b/hak/keytar/build.js
new file mode 100644
index 0000000..335149e
--- /dev/null
+++ b/hak/keytar/build.js
@@ -0,0 +1,45 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+const path = require('path');
+const childProcess = require('child_process');
+
+const mkdirp = require('mkdirp');
+const fsExtra = require('fs-extra');
+
+module.exports = async function(hakEnv, moduleInfo) {
+    await buildKeytar(hakEnv, moduleInfo);
+};
+
+async function buildKeytar(hakEnv, moduleInfo) {
+    const env = hakEnv.makeGypEnv();
+
+    console.log("Running yarn with env", env);
+    await new Promise((resolve, reject) => {
+        const proc = childProcess.spawn(
+            path.join(moduleInfo.nodeModuleBinDir, 'node-gyp' + (hakEnv.isWin() ? '.cmd' : '')),
+            ['rebuild'],
+            {
+                cwd: moduleInfo.moduleBuildDir,
+                env,
+                stdio: 'inherit',
+            },
+        );
+        proc.on('exit', (code) => {
+            code ? reject(code) : resolve();
+        });
+    });
+}
diff --git a/hak/keytar/check.js b/hak/keytar/check.js
new file mode 100644
index 0000000..921e0e4
--- /dev/null
+++ b/hak/keytar/check.js
@@ -0,0 +1,58 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+const childProcess = require('child_process');
+
+module.exports = async function(hakEnv, moduleInfo) {
+    // of course tcl doesn't have a --version
+    if (!hakEnv.isLinux()) {
+        await new Promise((resolve, reject) => {
+            const proc = childProcess.spawn('tclsh', [], {
+                stdio: ['pipe', 'ignore', 'ignore'],
+            });
+            proc.on('exit', (code) => {
+                if (code !== 0) {
+                    reject("Can't find tclsh - have you installed TCL?");
+                } else {
+                    resolve();
+                }
+            });
+            proc.stdin.end();
+        });
+    }
+
+    const tools = [['python', '--version']]; // node-gyp uses python for reasons beyond comprehension
+    if (hakEnv.isWin()) {
+        tools.push(['nmake', '/?']);
+    } else {
+        tools.push(['make', '--version']);
+    }
+
+    for (const tool of tools) {
+        await new Promise((resolve, reject) => {
+            const proc = childProcess.spawn(tool[0], tool.slice(1), {
+                stdio: ['ignore'],
+            });
+            proc.on('exit', (code) => {
+                if (code !== 0) {
+                    reject("Can't find " + tool);
+                } else {
+                    resolve();
+                }
+            });
+        });
+    }
+};
diff --git a/hak/keytar/fetchDeps.js b/hak/keytar/fetchDeps.js
new file mode 100644
index 0000000..8d27a6f
--- /dev/null
+++ b/hak/keytar/fetchDeps.js
@@ -0,0 +1,26 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+const path = require('path');
+const childProcess = require('child_process');
+
+const fs = require('fs');
+const fsProm = require('fs').promises;
+const needle = require('needle');
+const tar = require('tar');
+
+module.exports = async function(hakEnv, moduleInfo) {
+};
diff --git a/hak/keytar/hak.json b/hak/keytar/hak.json
new file mode 100644
index 0000000..6c9fe99
--- /dev/null
+++ b/hak/keytar/hak.json
@@ -0,0 +1,12 @@
+{
+    "scripts": {
+        "check": "check.js",
+        "fetchDeps": "fetchDeps.js",
+        "build": "build.js"
+    },
+    "prune": "native",
+    "copy": "build/Release/keytar.node",
+    "dependencies": {
+        "libsecret": "0.20.3"
+    }
+}
diff --git a/package.json b/package.json
index cb72b09..7ff4982 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,8 @@
     "tar": "^6.0.1"
   },
   "hakDependencies": {
-    "matrix-seshat": "^1.3.3"
+    "matrix-seshat": "^1.3.3",
+    "keytar": "^5.6.0"
   },
   "build": {
     "appId": "im.riot.app",
diff --git a/src/electron-main.js b/src/electron-main.js
index 34a6a5a..f66dfea 100644
--- a/src/electron-main.js
+++ b/src/electron-main.js
@@ -43,6 +43,18 @@ const Store = require('electron-store');
 const fs = require('fs');
 const afs = fs.promises;
 
+const crypto = require('crypto');
+let keytar;
+try {
+    keytar = require('keytar');
+} catch (e) {
+    if (e.code === "MODULE_NOT_FOUND") {
+        console.log("Keytar isn't installed; secure key storage is disabled.");
+    } else {
+        console.warn("Keytar unexpected error:", e);
+    }
+}
+
 let seshatSupported = false;
 let Seshat;
 let SeshatRecovery;
@@ -365,6 +377,40 @@ ipcMain.on('ipcCall', async function(ev, payload) {
             recordSSOSession(args[0]);
             break;
 
+        case 'getPickleKey':
+            try {
+                ret = await keytar.getPassword("riot.im", `${args[0]}|${args[1]}`);
+            } catch (e) {
+                // if an error is thrown (e.g. keytar can't connect to the keychain),
+                // then return null, which means the default pickle key will be used
+                ret = null;
+            }
+            break;
+
+        case 'createPickleKey':
+            try {
+                const randomArray = await new Promise((resolve, reject) => {
+                    crypto.randomBytes(32, (err, buf) => {
+                        if (err)
+                            reject(err);
+                        else
+                            resolve(buf);
+                    });
+                });
+                const pickleKey = randomArray.toString("base64").replace(/=+$/g, '');
+                await keytar.setPassword("riot.im", `${args[0]}|${args[1]}`, pickleKey);
+                ret = pickleKey;
+            } catch (e) {
+                ret = null;
+            }
+            break;
+
+        case 'destroyPickleKey':
+            try {
+                await keytar.deletePassword("riot.im", `${args[0]}|${args[1]}`);
+            } catch (e) {}
+            break;
+
         default:
             mainWindow.webContents.send('ipcReply', {
                 id: payload.id,

From b366dbb460324fe8c39990721d29d51612874f54 Mon Sep 17 00:00:00 2001
From: Hubert Chathi <hubert@uhoreg.ca>
Date: Thu, 28 May 2020 15:30:00 -0400
Subject: [PATCH 2/4] fix lint errors and add copyright info

---
 hak/keytar/build.js     |  3 ---
 hak/keytar/fetchDeps.js | 26 --------------------------
 hak/keytar/hak.json     |  1 -
 src/electron-main.js    |  1 +
 4 files changed, 1 insertion(+), 30 deletions(-)
 delete mode 100644 hak/keytar/fetchDeps.js

diff --git a/hak/keytar/build.js b/hak/keytar/build.js
index 335149e..39319ef 100644
--- a/hak/keytar/build.js
+++ b/hak/keytar/build.js
@@ -17,9 +17,6 @@ limitations under the License.
 const path = require('path');
 const childProcess = require('child_process');
 
-const mkdirp = require('mkdirp');
-const fsExtra = require('fs-extra');
-
 module.exports = async function(hakEnv, moduleInfo) {
     await buildKeytar(hakEnv, moduleInfo);
 };
diff --git a/hak/keytar/fetchDeps.js b/hak/keytar/fetchDeps.js
deleted file mode 100644
index 8d27a6f..0000000
--- a/hak/keytar/fetchDeps.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-const path = require('path');
-const childProcess = require('child_process');
-
-const fs = require('fs');
-const fsProm = require('fs').promises;
-const needle = require('needle');
-const tar = require('tar');
-
-module.exports = async function(hakEnv, moduleInfo) {
-};
diff --git a/hak/keytar/hak.json b/hak/keytar/hak.json
index 6c9fe99..28ee722 100644
--- a/hak/keytar/hak.json
+++ b/hak/keytar/hak.json
@@ -1,7 +1,6 @@
 {
     "scripts": {
         "check": "check.js",
-        "fetchDeps": "fetchDeps.js",
         "build": "build.js"
     },
     "prune": "native",
diff --git a/src/electron-main.js b/src/electron-main.js
index f66dfea..5144682 100644
--- a/src/electron-main.js
+++ b/src/electron-main.js
@@ -3,6 +3,7 @@ Copyright 2016 Aviral Dasgupta
 Copyright 2016 OpenMarket Ltd
 Copyright 2018, 2019 New Vector Ltd
 Copyright 2017, 2019 Michael Telatynski <7t3chguy@gmail.com>
+Copyright 2020 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.

From 188190888b11fb06abbdd0874ae348e48bc4331e Mon Sep 17 00:00:00 2001
From: Hubert Chathi <hubert@uhoreg.ca>
Date: Thu, 28 May 2020 15:36:06 -0400
Subject: [PATCH 3/4] more lint

---
 src/electron-main.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/electron-main.js b/src/electron-main.js
index 5144682..e7f261b 100644
--- a/src/electron-main.js
+++ b/src/electron-main.js
@@ -392,10 +392,11 @@ ipcMain.on('ipcCall', async function(ev, payload) {
             try {
                 const randomArray = await new Promise((resolve, reject) => {
                     crypto.randomBytes(32, (err, buf) => {
-                        if (err)
+                        if (err) {
                             reject(err);
-                        else
+                        } else {
                             resolve(buf);
+                        }
                     });
                 });
                 const pickleKey = randomArray.toString("base64").replace(/=+$/g, '');

From b4af8d9ce59caa163025007eb54c4f6fab49412f Mon Sep 17 00:00:00 2001
From: Hubert Chathi <hubert@uhoreg.ca>
Date: Wed, 3 Jun 2020 16:47:09 -0400
Subject: [PATCH 4/4] remove unneeded stuff

---
 hak/keytar/check.js | 22 ----------------------
 hak/keytar/hak.json |  1 -
 2 files changed, 23 deletions(-)

diff --git a/hak/keytar/check.js b/hak/keytar/check.js
index 921e0e4..8fcb788 100644
--- a/hak/keytar/check.js
+++ b/hak/keytar/check.js
@@ -17,29 +17,7 @@ limitations under the License.
 const childProcess = require('child_process');
 
 module.exports = async function(hakEnv, moduleInfo) {
-    // of course tcl doesn't have a --version
-    if (!hakEnv.isLinux()) {
-        await new Promise((resolve, reject) => {
-            const proc = childProcess.spawn('tclsh', [], {
-                stdio: ['pipe', 'ignore', 'ignore'],
-            });
-            proc.on('exit', (code) => {
-                if (code !== 0) {
-                    reject("Can't find tclsh - have you installed TCL?");
-                } else {
-                    resolve();
-                }
-            });
-            proc.stdin.end();
-        });
-    }
-
     const tools = [['python', '--version']]; // node-gyp uses python for reasons beyond comprehension
-    if (hakEnv.isWin()) {
-        tools.push(['nmake', '/?']);
-    } else {
-        tools.push(['make', '--version']);
-    }
 
     for (const tool of tools) {
         await new Promise((resolve, reject) => {
diff --git a/hak/keytar/hak.json b/hak/keytar/hak.json
index 28ee722..7597052 100644
--- a/hak/keytar/hak.json
+++ b/hak/keytar/hak.json
@@ -3,7 +3,6 @@
         "check": "check.js",
         "build": "build.js"
     },
-    "prune": "native",
     "copy": "build/Release/keytar.node",
     "dependencies": {
         "libsecret": "0.20.3"